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>
This commit is contained in:
Binary file not shown.
@@ -14,7 +14,8 @@ VERSION="${VERSION:-$(./daemon-version.py --stamp)}"
|
||||
|
||||
# 🔴 2026-08-06 機械閘:這一版的「更新內容」沒寫進 changelog ⇒ 不准打包。
|
||||
# leo:「給版本號、本版更新內容、打包產品、顯示在前端,這一整串都應該是機械化」。
|
||||
# 單一真相源=docs-site/.../help/changelog.md;manifest.notes 與前端全部投影自它。
|
||||
# 單一真相源=collector/CHANGELOG.md;manifest.notes 與前端全部投影自它。
|
||||
# (2026-08-18 D95 第一輪:從 docs-site 搬進 collector/,daemon 才算得出自己的版本。)
|
||||
# 理由與格式見 changelog-section.sh。
|
||||
./changelog-section.sh "$VERSION" --check
|
||||
|
||||
|
||||
@@ -79,8 +79,25 @@ echo "🏷 msix 版本:${MSIX_VERSION}(來源 ${VERSION_RAW})"
|
||||
# 我卻每次都當成「只有他有」而開口問。**腳本自己去讀,不要再問人。**
|
||||
# (對應鐵律:`system-dev/wiki/credentials-map.md` —— 用任何金鑰前先查那張表;
|
||||
# 查得到就自己拿,不要說「我沒有」。)
|
||||
ENV_FILE="${ENV_FILE:-$(cd "$(dirname "$0")" && git rev-parse --show-toplevel 2>/dev/null)/../../.env}"
|
||||
if [[ -z "${IDENTITY_NAME:-}" && -f "$ENV_FILE" ]]; then
|
||||
#
|
||||
# 🔴 2026-08-18(D95 第一輪):原本是 `git rev-parse --show-toplevel`/../../.env
|
||||
# ——那是**用本 repo 在磁碟上的位置**去推 InkStoneCo 頂層,
|
||||
# collector/ 一旦搬成獨立 repo(或只是被 clone 到別處)就指到不存在的路徑。
|
||||
# 改成**往上找那個帶著這把鍵的 .env**:leo 08-06 的要求原樣保留
|
||||
# (腳本自己去讀、不再問人,金鑰的家仍是頂層那一份),
|
||||
# 但**不再依賴目錄結構長什麼樣**。找不到就照舊落到 PLACEHOLDER 並印警告,
|
||||
# 不會安靜出一顆送不了審的 msix。
|
||||
if [[ -z "${ENV_FILE:-}" ]]; then
|
||||
_d="$(pwd)" # 腳本開頭已 cd 到自己的目錄
|
||||
while [[ "$_d" != "/" ]]; do
|
||||
if [[ -f "$_d/.env" ]] && grep -qE '^MS_STORE_ARCRUN_APP_NAME=' "$_d/.env" 2>/dev/null; then
|
||||
ENV_FILE="$_d/.env"; break
|
||||
fi
|
||||
_d="$(dirname "$_d")"
|
||||
done
|
||||
fi
|
||||
ENV_FILE="${ENV_FILE:-}"
|
||||
if [[ -z "${IDENTITY_NAME:-}" && -n "$ENV_FILE" && -f "$ENV_FILE" ]]; then
|
||||
# 只取這三個鍵,不把整包 .env 灌進環境(其他都是不該碰的金鑰)
|
||||
IDENTITY_NAME="$(grep -E '^MS_STORE_ARCRUN_APP_NAME=' "$ENV_FILE" | cut -d= -f2-)"
|
||||
PUBLISHER="$(grep -E '^MS_STORE_ARCRUN_APP_PUBLISHER=' "$ENV_FILE" | cut -d= -f2-)"
|
||||
|
||||
@@ -23,7 +23,7 @@ export PATH="$PATH:$(go env GOPATH)/bin"
|
||||
# 🔴 2026-08-06:版本號**不再手打、也不再靠 git describe**。
|
||||
# 舊寫法 `git describe --tags` 在這個 repo 永遠拿不到 v0.18.x(一個 tag 都沒有)
|
||||
# ⇒ 版本只能靠打包時人工 `VERSION=v0.18.4 ./build-win.sh` 敲 ⇒ manifest 說謊的病根。
|
||||
# 現在由 daemon-version.py 從 DAEMON_LINE + collector/ 的 commit 數算出來,
|
||||
# 現在由 daemon-version.py 從 collector/DAEMON_LINE + collector/CHANGELOG.md 算出來,
|
||||
# 兩條打包線(win/mac)在同一個 commit 上必得同一個號碼。理由全文見該腳本。
|
||||
VERSION="${VERSION:-$(./daemon-version.py --stamp)}"
|
||||
BUILD_TIME="$(date '+%Y%m%d-%H%M')"
|
||||
@@ -31,7 +31,8 @@ echo "🏷 版本:${VERSION}(build ${BUILD_TIME})"
|
||||
|
||||
# 🔴 2026-08-06 機械閘:這一版的「更新內容」沒寫進 changelog ⇒ 不准打包。
|
||||
# leo:「給版本號、本版更新內容、打包產品、顯示在前端,這一整串都應該是機械化」。
|
||||
# 單一真相源=docs-site/.../help/changelog.md;manifest.notes 與前端全部投影自它。
|
||||
# 單一真相源=collector/CHANGELOG.md;manifest.notes 與前端全部投影自它。
|
||||
# (2026-08-18 D95 第一輪:從 docs-site 搬進 collector/,daemon 才算得出自己的版本。)
|
||||
# 理由與格式見 changelog-section.sh。
|
||||
./changelog-section.sh "$VERSION" --check
|
||||
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
#
|
||||
# 答:改的是「**寫在哪**」,不是「誰來寫」。
|
||||
# 文字一定得人寫(機器編不出「語意搜尋什麼都搜不到」這種人話),
|
||||
# 但**只准寫一個地方**= docs-site/src/content/docs/help/changelog.md(用戶語言那份)。
|
||||
# 但**只准寫一個地方**= collector/CHANGELOG.md(用戶語言那份)。
|
||||
# 🔴 2026-08-18(D95 第一輪):這份檔案原本住在 repo 根的
|
||||
# `docs-site/src/content/docs/help/changelog.md`,而且**同時裝著兩條版本線**
|
||||
# (桌面版 v0.18.x + 雲端引擎 1.4.x)。已拆開:桌面版的搬進 `collector/`,
|
||||
# 雲端那條原地不動。理由:daemon 得往上伸手才拿得到自己的版本號=搬不成獨立 repo。
|
||||
# 其餘全是投影、不准手改(=頂層 principles「投影不可手改,要改去改源頭」):
|
||||
# · manifest.daemon.notes ← 打包時由本腳本抽出
|
||||
# · docs-site 的「版本說明」頁 ← 本來就是它自己
|
||||
@@ -20,8 +24,10 @@
|
||||
# ./changelog-section.sh v0.18.7 --check # 只檢查存不存在
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
||||
FILE="$REPO_ROOT/docs-site/src/content/docs/help/changelog.md"
|
||||
# 🔴 一律以**自己的位置**定位(往上兩層=collector/),不問 git、不問 repo 根——
|
||||
# `git rev-parse --show-toplevel` 就是往上伸手的入口,剪掉它才搬得成獨立 repo。
|
||||
COLLECTOR="$(cd ../.. && pwd)"
|
||||
FILE="$COLLECTOR/CHANGELOG.md"
|
||||
|
||||
VERSION="${1:?用法:changelog-section.sh <版本,例 v0.18.7> [--check]}"
|
||||
CHECK_ONLY="${2:-}"
|
||||
@@ -39,7 +45,7 @@ if [ -z "$(printf '%s' "$SECTION" | tr -d '[:space:]')" ]; then
|
||||
cat >&2 <<MSG
|
||||
❌ changelog 裡沒有 ${VERSION} 的段落,不准打包。
|
||||
|
||||
請在 docs-site/src/content/docs/help/changelog.md 最上面加一段:
|
||||
請在 collector/CHANGELOG.md 最上面加一段:
|
||||
|
||||
## ${VERSION}($(date '+%Y-%m-%d'))
|
||||
|
||||
@@ -55,7 +61,8 @@ fi
|
||||
|
||||
[ "$CHECK_ONLY" = "--check" ] && { echo " ✅ changelog 有 ${VERSION} 的段落"; exit 0; }
|
||||
|
||||
# 給 manifest.daemon.notes 用的投影——**只有一個投影器**:installer/scripts/daemon-notes.mjs
|
||||
# 給 manifest.daemon.notes 用的投影——**只有一個投影器**:同目錄的 daemon-notes.mjs
|
||||
# (2026-08-18 從 installer/scripts/ 搬來;那邊只剩一層薄殼轉呼叫本目錄這支。)
|
||||
#
|
||||
# 🔴 2026-08-08 修(leo 真機看到 v0.18.24 的更新畫面):
|
||||
# leo 原話「**不要這麼長的散文,簡短講改了什麼,細節去 docs 讀。**」
|
||||
@@ -71,4 +78,4 @@ fi
|
||||
# 現在改成委派給那唯一的投影器(只取每條的粗體標題、串成一行、超長就截並導去 docs),
|
||||
# 而 `installer/scripts/ship.mjs` 的 notes 步驟會在每次出貨時自動套用它。
|
||||
# ⇒ 兩邊同一份規則,不會漂移。上面的 --check 閘原樣保留(build-*.sh 仍靠它)。
|
||||
exec node "$REPO_ROOT/installer/scripts/daemon-notes.mjs" "$VERSION"
|
||||
exec node ./daemon-notes.mjs "$VERSION"
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* daemon-notes.mjs — 使用者在「版本與更新」畫面看到的那一行,**由 changelog 機械導出**
|
||||
*
|
||||
* ── 這支解什麼病(leo 2026-08-08 真機看到 v0.18.24 的更新畫面)────────────
|
||||
* leo 原話:「**不要這麼長的散文,簡短講改了什麼,細節去 docs 讀。**」
|
||||
*
|
||||
* 他看到的是**一整面文字牆**,而且 `**粗體**` 原樣露在畫面上。
|
||||
* 真兇不是文案沒寫好,是**出貨當下靠人手工排版**:
|
||||
* 出貨時用一段臨時 python 把 changelog 的換行折掉塞進 `manifest.daemon.notes`,
|
||||
* 於是四條變成一大段;那段轉換每次出貨都要重寫一次,而且**沒人檢查結果長什麼樣**。
|
||||
*
|
||||
* ⇒ 與「版本號由內容算」同一種解法:**這一行也由單一真相源導出,不由人當場捏**。
|
||||
*
|
||||
* ── 為什麼是「只取粗體標題、串成一行」──────────────────────────────────
|
||||
* 畫面那個欄位是**純文字**:`main.js:215` 是 `<div class="d">${esc(u.notes)}</div>`,
|
||||
* ① `esc()` ⇒ 任何 markdown 符號都會原樣露出來(leo 看到的 `**` 就是這樣來的)
|
||||
* ② HTML 不保留換行(`.d` 沒有 white-space:pre)⇒ 塞 `\n` 進去也**不會**變成分行
|
||||
* ⇒ 唯一能讀的形狀就是**一行短句**。而 changelog 每條的 `**粗體標題**` 本來就是
|
||||
* 那條的一句話摘要——直接拿它,不必另外維護第二份文案(第二份必然漂移)。
|
||||
*
|
||||
* ── 🔴 2026-08-18(D95 第一輪):搬進 collector/,且不再往上伸手 ──────────
|
||||
* 本檔原本住在 `installer/scripts/`,讀的是 repo 根的 `docs-site/.../changelog.md`。
|
||||
* 那讓 **daemon 的更新說明投影器住在 daemon 之外**,而它讀的檔也在 daemon 之外
|
||||
* ⇒ `collector/` 沒辦法自己交出「這一版對用戶意味什麼」這句話。
|
||||
*
|
||||
* 現在:實作住這裡,讀的是**同一棵樹裡的** `collector/CHANGELOG.md`(自我定位,不問 git)。
|
||||
* `installer/scripts/daemon-notes.mjs` 變成薄殼,轉呼叫本檔——
|
||||
* **根可以往內伸手,collector 不可以往外伸手**,方向是單向的。
|
||||
*
|
||||
* 用法(collector 內部):
|
||||
* import { notesForVersion } from './daemon-notes.mjs';
|
||||
* notesForVersion('v0.18.24') // → 一行字,或 null(changelog 沒這版)
|
||||
*/
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { join, resolve } from 'node:path';
|
||||
|
||||
/** daemon 的 changelog=`collector/CHANGELOG.md`。由本檔位置往上兩層推出來,不問 git、不問 repo 根。 */
|
||||
export const CHANGELOG_PATH = join(import.meta.dirname, '..', '..', 'CHANGELOG.md');
|
||||
/** 畫面上一行讀得完的上限。超過就截,並改叫使用者去看說明文件。 */
|
||||
export const NOTES_MAX = 100;
|
||||
const TAIL = '(細節見說明文件)';
|
||||
|
||||
/** 把 markdown 行內語法剝成純文字——畫面不渲染 markdown,留著就是雜訊。 */
|
||||
export function stripMarkdown(s) {
|
||||
return String(s)
|
||||
.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1') // [字](連結) → 字
|
||||
.replace(/[*_`]+/g, '') // 粗體/斜體/行內碼標記
|
||||
.replace(/\s+/g, ' ') // 換行與連續空白 → 單一空白
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 從 changelog 取某一版的「一句話摘要」清單。
|
||||
* 規則:只認**頂層條目**(行首 `- `)的第一個粗體片段——那就是該條的標題。
|
||||
* 沒有粗體的條目退而取整行(截短),因為「有寫總比漏掉好」。
|
||||
*/
|
||||
export function headlinesFor(changelogText, version) {
|
||||
const lines = changelogText.split('\n');
|
||||
const start = lines.findIndex((l) => new RegExp(`^##\\s+${version.replace(/[.\\]/g, '\\$&')}(\\D|$)`).test(l.trim()));
|
||||
if (start < 0) return null;
|
||||
const out = [];
|
||||
for (let i = start + 1; i < lines.length; i++) {
|
||||
const l = lines[i];
|
||||
if (/^##\s/.test(l)) break; // 下一版開始
|
||||
if (!/^-\s/.test(l)) continue; // 只取頂層條目(續行、巢狀一律略過)
|
||||
const body = l.replace(/^-\s*/, '');
|
||||
const bold = body.match(/\*\*([^*]+)\*\*/);
|
||||
let h = stripMarkdown(bold ? bold[1] : body);
|
||||
h = h.replace(/^[^\p{L}\p{N}「((]+/u, ''); // 去掉開頭的 emoji/符號
|
||||
h = h.replace(/[::,,。.]+$/, ''); // 去掉尾標點(要串接)
|
||||
if (h) out.push(h);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 組成畫面上那一行。回傳 null=changelog 裡沒有這一版(呼叫端該當成錯誤)。
|
||||
* `changelogPath` 只給測試/薄殼覆寫用;正常呼叫不帶,走 collector 自己的 CHANGELOG.md。
|
||||
*/
|
||||
export function notesForVersion(version, changelogPath = CHANGELOG_PATH) {
|
||||
if (!existsSync(changelogPath)) return null;
|
||||
const heads = headlinesFor(readFileSync(changelogPath, 'utf8'), version);
|
||||
if (!heads || !heads.length) return null;
|
||||
|
||||
let line = heads.join('・');
|
||||
if (line.length > NOTES_MAX) {
|
||||
// 截到「最後一個完整條目」為止,再掛尾巴——不要把句子切一半給使用者看。
|
||||
const kept = [];
|
||||
for (const h of heads) {
|
||||
if ([...kept, h].join('・').length + TAIL.length > NOTES_MAX) break;
|
||||
kept.push(h);
|
||||
}
|
||||
line = (kept.length ? kept.join('・') : heads[0].slice(0, NOTES_MAX - TAIL.length)) + TAIL;
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
/**
|
||||
* 機械閘用:這一行本身可不可以送到使用者眼前?
|
||||
* 回傳問題清單(空=通過)。這道閘存在的理由=**手寫的那一行沒有任何人檢查**。
|
||||
*/
|
||||
export function checkNotes(notes) {
|
||||
const problems = [];
|
||||
const s = String(notes ?? '');
|
||||
if (!s.trim()) return ['manifest.daemon.notes 是空的——使用者按「檢查更新」看不到這版改了什麼'];
|
||||
if (/[*_`#]|\]\(/.test(s)) {
|
||||
problems.push(`manifest.daemon.notes 裡有 markdown 符號,畫面是純文字會原樣露出來:${JSON.stringify(s.slice(0, 60))}`);
|
||||
}
|
||||
if (/\n/.test(s)) {
|
||||
problems.push('manifest.daemon.notes 有換行——畫面不保留換行(.d 沒有 white-space:pre),會擠成一坨');
|
||||
}
|
||||
if (s.length > NOTES_MAX + TAIL.length) {
|
||||
problems.push(`manifest.daemon.notes 太長(${s.length} 字,上限 ${NOTES_MAX + TAIL.length})——leo 08-08:「不要這麼長的散文,簡短講改了什麼,細節去 docs 讀」`);
|
||||
}
|
||||
return problems;
|
||||
}
|
||||
|
||||
// CLI:印出某版會顯示的那一行(出貨前想先看一眼時用)
|
||||
// 🔴 2026-08-18:判斷「是不是直接跑本檔」要比**絕對路徑**,不能比檔名尾綴——
|
||||
// `installer/scripts/daemon-notes.mjs` 薄殼同名,用尾綴比會讓兩支 CLI 一起開火
|
||||
// (實撞:問雲端版號時本檔先 process.exit(1),薄殼根本沒機會查 docs-site)。
|
||||
if (process.argv[1] && resolve(process.argv[1]) === import.meta.filename) {
|
||||
const v = process.argv[2];
|
||||
if (!v) { console.error('用法:node collector/cmd/arcrun-app/daemon-notes.mjs <版本,例 v0.18.24>'); process.exit(2); }
|
||||
const line = notesForVersion(v);
|
||||
if (!line) { console.error(`❌ changelog 沒有 ${v} 這一版(${CHANGELOG_PATH})`); process.exit(1); }
|
||||
// stdout **只有那一行**——它會被別的腳本(changelog-section.sh)直接取用,
|
||||
// 多印一個字就會被塞進 manifest。其餘一律走 stderr。
|
||||
console.log(line);
|
||||
console.error(`(${line.length} 字)`);
|
||||
const probs = checkNotes(line);
|
||||
if (probs.length) { probs.forEach((p) => console.error('❌ ' + p)); process.exit(1); }
|
||||
console.error('✅ 可以送到使用者眼前');
|
||||
}
|
||||
@@ -15,8 +15,24 @@
|
||||
但**每個 commit 都變成一版**(一天 5 個 commit 就跳 5 版),
|
||||
而且每跳一版就要補一段 changelog——把機械化變成新的手工活。
|
||||
|
||||
── 🔴 2026-08-18(D95 第一輪):所有輸入都搬進 collector/ 了 ────────────
|
||||
leo 08-17:「身為管理者,你要從頭到尾**不要有很多扭曲**,因為你根本不記得
|
||||
你做的這些扭曲,**每次都要查**,很直接,源碼、產出物,從 stage 到 prod。」
|
||||
|
||||
本腳本原本往 **repo 根**伸手拿三樣東西:
|
||||
① docs-site/src/content/docs/help/changelog.md(版本與更新說明)
|
||||
② ROOT/DAEMON_LINE(版本線)
|
||||
③ `git ls-files collector`(原始碼指紋,從 repo 根算)
|
||||
⇒ `collector/` 算不出自己的版本,也**沒有資格被搬成獨立 repo**。
|
||||
|
||||
現在三樣全在 `collector/` 底下:`CHANGELOG.md`/`DAEMON_LINE`/指紋以 collector 為根。
|
||||
本檔一律以**自己的位置**定位(`__file__` 往上兩層=collector/),
|
||||
**不再呼叫 `git rev-parse --show-toplevel`**——那是往上伸手的入口。
|
||||
(三道既有修補全部原樣保留:指紋演算法版本作廢重記、帳本自我參照排除、
|
||||
沒有產物的版號可重戳。只有「以什麼為根」變了。)
|
||||
|
||||
── 現在的做法:版本由 changelog 決定,且不用手打數字 ──────────────
|
||||
單一真相源= docs-site/src/content/docs/help/changelog.md(用戶語言那份)。
|
||||
單一真相源= collector/CHANGELOG.md(用戶語言那份)。
|
||||
|
||||
· 要出新版 ⇒ 在檔案最上面加一段標題 `## 下一版(未發佈)`,底下寫白話更新內容。
|
||||
· 打包時本腳本把它**戳成正式版號**(上一版 patch + 1)並補上今天日期。
|
||||
@@ -40,17 +56,21 @@ 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"
|
||||
HERE = Path(__file__).resolve().parent # collector/cmd/arcrun-app
|
||||
# 🔴 daemon 的根=`collector/`,用**自己的位置**推出來,不問 git 也不問 repo 根。
|
||||
# 這一行就是「臍帶剪掉了」的本體:往上兩層剛好是 collector/,再往上一步都不走。
|
||||
COLLECTOR = HERE.parents[1]
|
||||
CHANGELOG = COLLECTOR / "CHANGELOG.md"
|
||||
LINE_FILE = COLLECTOR / "DAEMON_LINE"
|
||||
|
||||
UNRELEASED = "## 下一版(未發佈)"
|
||||
# 版本 → 當時原始碼指紋。用來擋「同一個版號、不同的執行檔」。
|
||||
SOURCE_LOCK = HERE / ".version-source.json"
|
||||
# 指紋演算法版本:改算法時 +1,帳本會自動作廢重記(見 check_or_record)。
|
||||
FINGERPRINT_ALGO = 3
|
||||
# 🔴 3 → 4(2026-08-18):指紋的**根**從 repo 根換成 collector/,被雜湊的相對路徑
|
||||
# 因此全部改變(`collector/cmd/…` → `cmd/…`)⇒ 舊帳本是「用不同單位量出來的數字」,
|
||||
# 比對沒有意義。照既有設計改號讓它自動整本作廢重記,不要手改 JSON。
|
||||
FINGERPRINT_ALGO = 4
|
||||
RELEASED_RE = re.compile(r"^## v(\d+)\.(\d+)\.(\d+)(", re.M)
|
||||
|
||||
|
||||
@@ -72,11 +92,17 @@ def source_fingerprint():
|
||||
會給出不同指紋、即使內容一模一樣——戳版號時檔案還沒提交(在 diff 裡),
|
||||
提交後樹變了、diff 空了。結果只改 wiki 也被判「版號已對應另一份原始碼」。
|
||||
改成對工作區檔案內容取值,與有沒有提交無關。
|
||||
|
||||
四修(2026-08-18,D95 第一輪):
|
||||
改成**以 `collector/` 為根**列檔(cwd=COLLECTOR、pathspec `.`),
|
||||
不再從 repo 根列 `collector` 這個子目錄。涵蓋的檔案集合完全相同,
|
||||
差別只有相對路徑前綴——而路徑有進雜湊,所以 FINGERPRINT_ALGO 跟著 +1。
|
||||
這樣 collector/ 搬成獨立 repo 之後,同一段程式碼算出的仍是同一個值。
|
||||
"""
|
||||
try:
|
||||
files = subprocess.check_output(
|
||||
["git", "ls-files", "-co", "--exclude-standard", "collector"],
|
||||
cwd=ROOT, text=True).split("\n")
|
||||
["git", "ls-files", "-co", "--exclude-standard", "."],
|
||||
cwd=COLLECTOR, text=True).split("\n")
|
||||
except subprocess.CalledProcessError:
|
||||
return "" # 不在 git 裡就不擋(例如從 tarball 解出來 build)
|
||||
# 🔴 2026-08-06 三修:**帳本自己不能算進指紋**(經典的自我參照)。
|
||||
@@ -86,11 +112,11 @@ def source_fingerprint():
|
||||
# dist/ 也早就 gitignore,不在名單裡。真兇只有帳本。)
|
||||
# 只排除帳本一個檔——**不要順手把 build/ 整個排掉**,
|
||||
# 那會讓「換 app icon」不算原始碼變更,等於把閘挖個洞。
|
||||
skip = {str(SOURCE_LOCK.relative_to(ROOT))}
|
||||
skip = {str(SOURCE_LOCK.relative_to(COLLECTOR))}
|
||||
|
||||
h = hashlib.sha256()
|
||||
for rel in sorted(f for f in files if f.strip() and f not in skip):
|
||||
fp = ROOT / rel
|
||||
fp = COLLECTOR / rel
|
||||
if not fp.is_file():
|
||||
continue # 已刪除的檔案
|
||||
h.update(rel.encode())
|
||||
|
||||
Reference in New Issue
Block a user