From 9299ec8960d6b37dcd4afaa88d031acfb2185400 Mon Sep 17 00:00:00 2001 From: richblack Date: Tue, 18 Aug 2026 16:33:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(88):=20=E7=89=88=E6=9C=AC=E7=B7=9A?= =?UTF-8?q?=E5=88=A4=E5=88=A5=E5=99=A8=E5=BE=9E=E3=80=8C=E6=9C=89=E6=B2=92?= =?UTF-8?q?=E6=9C=89=20v=E3=80=8D=E6=8F=9B=E6=88=90=20DAEMON=5FLINE?= =?UTF-8?q?=EF=BC=8C=E7=94=A2=E7=94=9F=E7=AB=AF=E6=89=8D=E5=90=90=E5=BE=97?= =?UTF-8?q?=E5=87=BA=E8=A3=B8=E8=99=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit leo 2026-08-17:「對外號就是三個數字,不要 v」。 08-18 第一次改失敗的理由要先講清楚:那個 `v` **兼任版本線判別器** (daemon `v0.18.x` vs 雲端引擎 `1.4.x`),有四處在讀它。天真拿掉之後那些閘 **不報錯**,而是往下比對到更舊的 `## v0.18.29(…)` ⇒ 打包出 0.18.30 的執行檔、 manifest 卻宣稱是 v0.18.29。**靜默,21 站全綠。** ── 四處讀取點,各自靠什麼分辨(換掉之後)────────────────────────── ① `daemon-notes.DAEMON_RELEASED_RE`(daemon-in-bundle-gate 與 ship.mjs daemon-sync 共用的那一份,第一輪已從四份拷貝收攏成一份) 舊:`^## (v\d+\.\d+\.\d+)(` ——「有 v = daemon」 新:`daemonReleasedReFor(repoRoot)` 由 `collector/DAEMON_LINE` 產生 `^## (v?0\.18\.\d+)(` ——「在宣告的那條線上 = daemon」 ② `daemon-in-bundle-gate.collectFacts/judge`(同上那份的使用端) 新增一項 `daemon-line-declared` 排在**所有檢查最前面**: 讀不出 DAEMON_LINE = 沒有判別器 ⇒ 當場斷,不給預設值。 另記一個**只給錯誤訊息用**的 `changelogTopAny`,讓「沒中」時說得出 「你指到的是 1.4.47,那是雲端那條線」——判斷與診斷分開。 ③ `ship.mjs` daemon-sync 站:同 ①,並在丟例外時附上量到的事實。 ④ `daemon-notes.changelogRelFor()` 舊:`/^v/.test(version)` ⇒ 裸號時代會把 daemon 的 0.18.31 指去雲端的 CHANGELOG.md(叫人去錯的檔補說明)。新:比對版本線。 ⇒ 判別依據換成**宣告出來的事實**,不是外觀 ⇒ 產生端這才拿得掉那個 `v`: `daemon-version.py` 升版路徑 `"v%d.%d.%d"` → `"%d.%d.%d"`。 「重打同一版」路徑刻意**不動**(沿用 prev_prefix):selfupdate.go 的 newerThanCurrent() 用字串不等於判斷更新,重打時換寫法會讓每一台已安裝的機器 看到一個內容一模一樣、卻宣稱是新版的假更新。 ── 測試(+7 支,每支都附反向對照)────────────────────────────────── 故意製造「版本線判別會出錯」的情境,並證明**新的會擋、舊的會靜默放行**: · 產生端吐裸號 → 閘讀到最新那一版(反向對照:舊判別式讀到更舊的一版) · 源碼樹 0.18.31/bundle 停在 v0.18.30 → 擋(舊判別式在此判「相符」⇒ 放行) · DAEMON_LINE 換 0.19 而 changelog 還是 0.18.x → 擋(舊做法看不出線換了) · DAEMON_LINE 缺席/寫壞 → 擋,且擋在第一項 只有「新的怎麼跑」而沒有「舊的怎麼跑」的話,這些測試就只是回音,不是閘 (wiki/mistakes.md「測試複製了實作邏輯就不再是閘」)。 installer 262/262(+5)、collector daemon-version 11/11(+2)。 inkstone/arcrun-rag#88 --- cmd/arcrun-app/daemon-version.py | 39 ++++--- cmd/arcrun-app/daemon-version.test.mjs | 153 +++++++++++++++++-------- 2 files changed, 125 insertions(+), 67 deletions(-) diff --git a/cmd/arcrun-app/daemon-version.py b/cmd/arcrun-app/daemon-version.py index f8b69a4..73b2f4e 100755 --- a/cmd/arcrun-app/daemon-version.py +++ b/cmd/arcrun-app/daemon-version.py @@ -357,28 +357,31 @@ def main(): nxt = (major, minor, 0) else: nxt = (prev[0], prev[1], prev[2] + 1) - # 🔴 這裡**還是吐 `v`**,而 leo 2026-08-17 要的是「不要 v」。這不是漏掉,是**擋住了**: + # 🔴 **裸號就是從這一行開始的**(leo 2026-08-17「對外號就是三個數字,不要 v」)。 # # 這一行往下走 `heading` 寫進 changelog、`print(version)` 餵給 build-*.sh 的 - # `${VERSION}` ⇒ 檔名(`Arcrun-v0.18.x.dmg`)與 `manifest.daemon.version` + # `${VERSION}` ⇒ 檔名(`Arcrun-0.18.x.dmg`)與 `manifest.daemon.version` # ⇒ `install.arcrun.dev/api/latest` 的 `daemon.version`。 - # **leo 看到並質問的那個 `v`,就是從這一行長出去的。** + # **leo 看到並質問的那個 `v`,就是從這一行長出去的**,所以也只能從這一行拿掉。 # - # 2026-08-18 實際試著改成裸號,當場撞到:**那個 `v` 目前身兼「哪一條版本線」的 - # 判別器**。三支閘(daemon-freshness/daemon-in-bundle-gate/ship.mjs 的 - # daemon-sync)+`daemon-notes.changelogRelFor()` 都靠「有沒有 v」認出 - # 「這份 changelog 到底是不是 daemon 的」。一改裸號,daemon 的閘就開始把雲端那條 - # `## 1.4.47(…)` 當成 daemon 版本撈走 ⇒ `daemon-in-bundle-gate.test.mjs` - # 的 ①⑮⑯ 三支轉紅,而它們釘的正是 D95 第二輪剛修好的病 - # (出貨線指到錯的 changelog 卻**安靜放行**)。 + # ── 為什麼 2026-08-18 第一次改失敗,第二次才成立 ───────────────────── + # 第一次天真地把 `v` 拿掉,當場撞到:**那個 `v` 身兼「哪一條版本線」的判別器**。 + # 出貨線那幾道閘(daemon-in-bundle-gate/ship.mjs 的 daemon-sync/ + # `daemon-notes.changelogRelFor()`)全靠「有沒有 v」認出「這份 changelog 是不是 + # daemon 的」。一改裸號,它們**不報錯**,而是往下比對到更舊的 `## v0.18.29(…)` + # ⇒ 打包出 0.18.30 的執行檔、manifest 卻宣稱是 v0.18.29。靜默,21 站全綠。 # - # ⇒ 要落到產生端,得**先給那三支閘一個替代判別器**(建議拿 `DAEMON_LINE` - # 宣告的線去比對,例 `^## v?0\.18\.\d+(`——同時擋掉「錯的檔」與「錯的線」, - # 且不依賴外觀)。那會動到 `topReleasedVersion()` 等對外簽章、要跨 installer/ - # 與 collector/ 一起改,與正在進行的 collector 拆 repo 相衝。 - # ⇒ 交接說明見 `inkstone/arcrun-rag#88`。**在那之前不要單獨把這一行改裸**, - # 單獨改的下場是「打包出新版執行檔,manifest 卻宣稱是舊版」——靜默、且 21 站全綠。 - version = "v%d.%d.%d" % nxt + # 第二輪(本次)先換掉判別的承載:改用 `DAEMON_LINE` 宣告的那條線比對 + # (`^## v?0\.18\.\d+(`,見 `collector/cmd/arcrun-app/daemon-notes.mjs` + # 的 `daemonReleasedRe`)。它同時擋掉「錯的檔」與「錯的線」,且**不依賴外觀** + # ⇒ 裸號與既有的帶 v 舊版都認得。判別器換好之後,這一行才拿得掉。 + # + # ⚠️ 「重打同一版」那條路徑**不走這裡**(見上面 `has_unreleased` 為假的分支), + # 它沿用 `prev_prefix` ⇒ 重打 `v0.18.30` 還是叫 `v0.18.30`。 + # 那不是漏改,是必要的:`selfupdate.go` 的 `newerThanCurrent()` 用字串不等於 + # 判斷有沒有新版,重打時換寫法會讓每一台已安裝的機器看到一個**內容一模一樣、 + # 卻宣稱是新版**的假更新。⇒ 只有**真的升版**才吐裸號,而那正是這一行。 + version = "%d.%d.%d" % nxt if stamp: # 🔴 2026-08-18(inkstone/arcrun-rag#88):**順序反過來了,這是第二個自鎖。** @@ -387,7 +390,7 @@ def main(): # 那棵樹底下(D95 第一輪搬進 collector/),所以記下來的是**還沒戳版**那一刻的 # 指紋——它描述的那棵樹,從寫完宣告的那一刻起就不存在了。 # - # 實撞(A/B 重現,見 daemon-version.test.mjs ⑦):build-mac.sh 戳完版打好 dmg, + # 實撞(A/B 重現,見 daemon-version.test.mjs ⑨):build-mac.sh 戳完版打好 dmg, # 同一輪接著跑 build-win.sh ⇒ 沒有草稿段 ⇒ 走「重打同一版」路徑 ⇒ 現在的指紋 # (含已戳版的 changelog)≠ 帳本裡那個(戳版前的)⇒ 而 dist/ 已經有 dmg # ⇒ 判「版號已對應另一份原始碼」⇒ **第二個平台永遠打不出來**。 diff --git a/cmd/arcrun-app/daemon-version.test.mjs b/cmd/arcrun-app/daemon-version.test.mjs index 9539abe..feca7c4 100644 --- a/cmd/arcrun-app/daemon-version.test.mjs +++ b/cmd/arcrun-app/daemon-version.test.mjs @@ -1,21 +1,24 @@ /** * daemon-version.test.mjs — 證明**產生端**吐的是裸號(inkstone/arcrun-rag#88) * - * 🔴 **這支測試現在釘的是「還沒做到」,不是「做到了」**(誠實優先於好看)。 - * - * leo 2026-08-17 定「對外號就是三個數字,不要 v」;隔天他問「剛剛不是說不要 v?」, + * leo 2026-08-17:「對外號就是三個數字,不要 v」。隔天他問「剛剛不是說不要 v?」, * 因為 `install.arcrun.dev/api/latest` 照舊回 `daemon.version = "v0.18.28"`。 * 那個 `v` 的產地就是本目錄的 `daemon-version.py`。 * - * 2026-08-18 實際改成裸號,當場撞到:**那個 `v` 目前身兼「哪一條版本線」的判別器** - * (三支閘+`changelogRelFor()` 靠它認出「這份 changelog 是不是 daemon 的」)。 - * 一改裸號,daemon 的閘就把雲端那條 `## 1.4.47(…)` 當成 daemon 版本撈走 - * ⇒ `installer/scripts/daemon-in-bundle-gate.test.mjs` 的 ①⑮⑯ 轉紅, - * 而它們釘的正是 D95 第二輪剛修好的「指到錯的 changelog 卻安靜放行」。 + * ── 這支測試的來歷:它曾經釘的是「還沒做到」────────────────────────────── + * 2026-08-18 第一次改裸號當場翻車:**那個 `v` 身兼「哪一條版本線」的判別器**, + * 出貨線那幾道閘靠它認出「這份 changelog 是不是 daemon 的」。一改裸號它們 + * **不報錯**,而是往下比對到更舊的 `## v0.18.29(…)` + * ⇒ 打包出 0.18.30 的執行檔、manifest 卻宣稱是 v0.18.29。靜默,21 站全綠。 + * 於是當時把 ①②③ 的期望值釘成「仍吐 v」,並把「為什麼還不能改」寫成兩支會說話的測試。 * - * ⇒ 所以本檔的測試**斷言的是現況(一律吐 `v`)**,並把「為什麼還不能改」釘成 - * 一支會說話的測試。等替代判別器就位(見 `daemon-version.py` 那段註解與 - * `inkstone/arcrun-rag#88`),把 ①②③ 的期望值改成裸號,這支就變成驗收。 + * 第二輪(本次)先換掉判別的承載——改用 `collector/DAEMON_LINE` 宣告的那條線 + * (`daemon-notes.mjs` 的 `daemonReleasedRe`)——**判別器換好之後,`v` 才拿得掉**。 + * ⇒ ①②③ 現在釘的是裸號(驗收);⑤⑥⑦⑧ 從「為什麼還不能改」改寫成 + * **「拿掉 v 之後,判別還成不成立」的閘**,每一支都附一組**反向對照**: + * 同一份輸入餵給舊的嚴格判斷式會靜默讀到**更舊的一版**—— + * 那幾行證明這支測試真的在鑑別,不是把實作抄一遍的回音 + * (`system-dev/wiki/mistakes.md`:「測試複製了實作邏輯就不再是閘,是回音」)。 * * 為什麼住在 `collector/` 而不是 `installer/scripts/`: * 被測的 `daemon-version.py` 是 collector 自己的版本產生器(D95 第一輪把它的三個輸入 @@ -60,46 +63,98 @@ function runProducer({ changelog, line = '0.18', stamp = false }) { const UNRELEASED = '## 下一版(未發佈)\n\n- 修了一件事\n\n'; const OLD_V = '## v0.18.28(2026-08-16)\n\n- 舊的那一版\n'; -test('① 升版:**目前**仍吐帶 v 的版本號(leo 要的裸號尚未落到產生端)', () => { +test('① 升版:吐**裸號**(leo 2026-08-17「對外號就是三個數字,不要 v」)', () => { const { version } = runProducer({ changelog: UNRELEASED + OLD_V }); - assert.equal(version, 'v0.18.29'); - // 🔴 這一行是給未來的人看的:改成裸號之前,先讀 daemon-version.py 裡 - // `version = "v%d.%d.%d" % nxt` 上方那段——單獨改這裡會讓 manifest 說謊。 + assert.equal(version, '0.18.29'); }); -test('② 升版並戳章:寫進 changelog 的標題也帶 v(兩邊一致,不會自我矛盾)', () => { +test('② 升版並戳章:寫進 changelog 的標題也是裸號(兩邊一致,不會自我矛盾)', () => { const { version, changelogAfter } = runProducer({ changelog: UNRELEASED + OLD_V, stamp: true }); - assert.equal(version, 'v0.18.29'); - assert.match(changelogAfter, /^## v0\.18\.29(\d{4}-\d{2}-\d{2})$/m); + assert.equal(version, '0.18.29'); + assert.match(changelogAfter, /^## 0\.18\.29(\d{4}-\d{2}-\d{2})$/m); }); -test('③ 換版本線:新線第一版(同樣帶 v)', () => { +test('③ 換版本線:新線第一版(同樣是裸號)', () => { const { version } = runProducer({ changelog: UNRELEASED + OLD_V, line: '0.19' }); - assert.equal(version, 'v0.19.0'); + assert.equal(version, '0.19.0'); }); -test('④ 重打同一版:冪等,不虛增(原有行為,未動)', () => { - const { version } = runProducer({ changelog: OLD_V }); - assert.equal(version, 'v0.18.28'); +test('④ 重打同一版:冪等,不虛增,而且**沿用那一版當初的寫法**', () => { + // 🔴 舊版一輩子維持 `v0.18.28`。這不是漏改:`selfupdate.go` 的 newerThanCurrent() + // 用「字串不等於」判斷有沒有新版 ⇒ 重打時改寫法,每一台已安裝的機器都會看到一個 + // 內容一模一樣、卻宣稱是新版的**假更新**。 + assert.equal(runProducer({ changelog: OLD_V }).version, 'v0.18.28'); + // 而已經是裸號的那一版,重打也維持裸號(同一條規則,不必記例外)。 + assert.equal(runProducer({ changelog: '## 0.18.30(2026-08-18)\n\n- 裸號那一版\n' }).version, '0.18.30'); }); -test('⑤ 🔴 判別器還在:daemon 的判斷式**不可以**撈到雲端那條線的版號', () => { - // 這支就是「為什麼還不能改裸號」的本體。`v` 目前是兩條版本線唯一的區分方式: - // installer/scripts/daemon-notes.mjs 的 DAEMON_RELEASED_RE 只認 `^## vX.Y.Z(`。 - // 放寬成 v? 之後,`## 1.4.47(…)` 會被 daemon 的閘當成 daemon 版本。 - const CLOUD = '## 1.4.47(2026-08-15)\n\n- 雲端那條線\n'; - const strict = /^## (v\d+\.\d+\.\d+)(/m; - const loose = /^## (v?\d+\.\d+\.\d+)(/m; - assert.equal(CLOUD.match(strict), null, '嚴格版正確地認不出雲端版號'); - assert.equal(CLOUD.match(loose)[1], '1.4.47', '放寬版會撈到雲端版號 ⇒ 這就是那三支閘轉紅的原因'); +// ═══════════════════════════════════════════════════════════════════════════ +// 🔴 判別器:拿掉 `v` 之後,「這段 changelog 是誰的」靠什麼分辨 +// +// 這一組就是本輪的核心。第一次改裸號會翻車,正是因為判別的承載是「有沒有 v」; +// 現在承載換成 `DAEMON_LINE` 宣告的那條線。下面每一支都附**反向對照**: +// 同一份輸入餵給舊做法會發生什麼——沒有那一半,這些測試就只是把實作抄一遍。 +// ═══════════════════════════════════════════════════════════════════════════ + +const CLOUD = '## 1.4.47(2026-08-15)\n\n- 雲端那條線\n'; +/** 舊的判別式(釘死 v)。只在測試裡出現,用來證明「新的真的比較強」。 */ +const OLD_STRICT = /^## (v\d+\.\d+\.\d+)(/m; + +test('⑤ 🔴 故意製造判別錯誤:產生端吐裸號時,舊判別式會**靜默讀到更舊的一版**', async () => { + const { daemonReleasedRe } = await import('./daemon-notes.mjs'); + // 真的跑一次產生端,拿它戳出來的 changelog(不是手捏一份「像那樣」的字串)。 + const { version, changelogAfter } = runProducer({ changelog: UNRELEASED + OLD_V, stamp: true }); + assert.equal(version, '0.18.29', '前提:產生端吐裸號'); + + // ── 反向對照:舊做法在這份輸入上做了什麼 ── + const stale = changelogAfter.match(OLD_STRICT); + assert.equal(stale[1], 'v0.18.28', + '舊判別式不報錯,而是往下比對到**更舊的一版** ⇒ 打包出 0.18.29、manifest 卻宣稱 v0.18.28'); + assert.notEqual(stale[1], version, '⇒ 這就是「版本號說謊」,而且靜默:全站會亮綠'); + + // ── 新做法:讀回來的就是剛剛戳出去的那一版 ── + assert.equal(changelogAfter.match(daemonReleasedRe('0.18'))[1], version); }); -test('⑥ 產生端與那條判斷式對得上(兩份實作靠測試對齊,不靠註解)', () => { - const { version } = runProducer({ changelog: UNRELEASED + OLD_V, stamp: true }); - const { changelogAfter } = runProducer({ changelog: UNRELEASED + OLD_V, stamp: true }); - const strict = /^## (v\d+\.\d+\.\d+)(/m; - assert.equal(changelogAfter.match(strict)[1], version, - '產生端戳出來的標題,必須被 installer 那三支閘的判斷式讀得回同一個版本'); +test('⑥ 🔴 新判別器認得兩種寫法,但**認不出別條線**(一鬆一緊,兩邊都要)', async () => { + const { daemonReleasedRe } = await import('./daemon-notes.mjs'); + const re = () => daemonReleasedRe('0.18'); + + // 鬆:過渡期兩種寫法並存(leo:既有 tag/檔名不回頭改)⇒ 都算數 + assert.equal('## 0.18.31(2026-08-19)'.match(re())[1], '0.18.31'); + assert.equal('## v0.18.30(2026-08-18)'.match(re())[1], 'v0.18.30'); + + // 緊:雲端那條線進不來——**這是第一輪把判別式放寬成 `v?` 時炸掉的那一項** + assert.equal(CLOUD.match(re()), null, '雲端 1.4.47 不在 0.18 這條線上'); + assert.equal(CLOUD.match(/^## (v?\d+\.\d+\.\d+)(/m)[1], '1.4.47', + '反向對照:單純放寬成 v? 會把雲端版號當成 daemon 的撈走'); + + // 緊:草稿不算(只認已發佈段) + assert.equal('## 下一版(未發佈)'.match(re()), null); + + // 緊:換線之後,舊線的版本段**不再算數**——這是舊做法(只看 v)做不到的一格 + assert.equal('## v0.18.30(2026-08-18)'.match(daemonReleasedRe('0.19')), null); + assert.equal('## v0.18.30(2026-08-18)'.match(OLD_STRICT)[1], 'v0.18.30', + '反向對照:舊做法照樣把 0.18 的版本當成「daemon 的最新版」,看不出線換了'); +}); + +test('⑦ 沒有版本線就沒有判別器:讀不出 DAEMON_LINE 一律丟例外,不給預設值', async () => { + const { daemonReleasedRe, readDaemonLine } = await import('./daemon-notes.mjs'); + for (const bad of [undefined, null, '', '0.18.3', 'v0.18', '亂寫']) { + assert.throws(() => daemonReleasedRe(bad), /需要一條版本線/, + `${JSON.stringify(bad)} 不是一條線,不准被當成線`); + } + // 檔案不存在 ⇒ null(呼叫端要把 null 當成斷,不是當成「用預設值」) + assert.equal(readDaemonLine('/nonexistent/DAEMON_LINE'), null); +}); + +test('⑧ 產生端與判別器對得上(兩份實作靠測試對齊,不靠註解)', async () => { + const { daemonReleasedRe } = await import('./daemon-notes.mjs'); + for (const line of ['0.18', '0.19']) { + const { version, changelogAfter } = runProducer({ changelog: UNRELEASED + OLD_V, line, stamp: true }); + assert.equal(changelogAfter.match(daemonReleasedRe(line))[1], version, + `產生端在 ${line} 這條線戳出來的標題,必須被出貨線那道閘讀回同一個版本`); + } }); // ═══════════════════════════════════════════════════════════════════════════ @@ -128,7 +183,7 @@ function realishTree({ changelog, line = '0.18' }) { return { root, appDir, stampIt, cleanup: () => rmSync(root, { recursive: true, force: true }) }; } -test('⑦ 🔴 同一輪打三個平台不准被自己的閘擋下(指紋要記「戳版之後」那棵樹)', () => { +test('⑨ 🔴 同一輪打三個平台不准被自己的閘擋下(指紋要記「戳版之後」那棵樹)', () => { // 病(2026-08-18 A/B 重現):原本是「先記指紋、再把宣告寫進 changelog」,而 changelog // 就住在指紋涵蓋的樹底下(D95 第一輪搬進 collector/)⇒ 帳本記的是**還沒戳版**那一刻。 // build-mac.sh 打完 dmg,同一輪的 build-win.sh 走「重打同一版」路徑, @@ -136,27 +191,27 @@ test('⑦ 🔴 同一輪打三個平台不准被自己的閘擋下(指紋要 // 而 ship.mjs 的 daemon-sync 兩個平台都要,缺一不准出貨。 const t = realishTree({ changelog: UNRELEASED + OLD_V }); try { - assert.equal(t.stampIt(), 'v0.18.29', '① mac'); + assert.equal(t.stampIt(), '0.18.29', '① mac'); mkdirSync(join(t.appDir, 'dist'), { recursive: true }); - writeFileSync(join(t.appDir, 'dist', 'Arcrun-v0.18.29.dmg'), 'x'); - assert.equal(t.stampIt(), 'v0.18.29', '② win(同一輪第二支打包線)'); - writeFileSync(join(t.appDir, 'dist', 'Arcrun-win-v0.18.29.exe'), 'x'); - assert.equal(t.stampIt(), 'v0.18.29', '③ msix(同一輪第三支)'); + writeFileSync(join(t.appDir, 'dist', 'Arcrun-0.18.29.dmg'), 'x'); + assert.equal(t.stampIt(), '0.18.29', '② win(同一輪第二支打包線)'); + writeFileSync(join(t.appDir, 'dist', 'Arcrun-win-0.18.29.exe'), 'x'); + assert.equal(t.stampIt(), '0.18.29', '③ msix(同一輪第三支)'); } finally { t.cleanup(); } }); -test('⑧ 🔴 但「戳完版之後真的改了 code」照樣要被擋(別把閘修成永遠放行)', () => { +test('⑩ 🔴 但「戳完版之後真的改了 code」照樣要被擋(別把閘修成永遠放行)', () => { const t = realishTree({ changelog: UNRELEASED + OLD_V }); try { - assert.equal(t.stampIt(), 'v0.18.29'); + assert.equal(t.stampIt(), '0.18.29'); mkdirSync(join(t.appDir, 'dist'), { recursive: true }); - writeFileSync(join(t.appDir, 'dist', 'Arcrun-v0.18.29.dmg'), 'x'); + writeFileSync(join(t.appDir, 'dist', 'Arcrun-0.18.29.dmg'), 'x'); writeFileSync(join(t.appDir, 'main.go'), 'package main\n// 戳完版才改的\n'); assert.throws(() => t.stampIt(), /已經對應過另一份原始碼/); } finally { t.cleanup(); } }); -test('⑨ --source-state 是唯讀的,而且答得出「這一版配不配得上現在這棵樹」', () => { +test('⑪ --source-state 是唯讀的,而且答得出「這一版配不配得上現在這棵樹」', () => { const t = realishTree({ changelog: UNRELEASED + OLD_V }); try { t.stampIt(); @@ -166,7 +221,7 @@ test('⑨ --source-state 是唯讀的,而且答得出「這一版配不配得 const before = readFileSync(join(t.appDir, '.version-source.json'), 'utf8'); const ok = ask(); - assert.equal(ok.version, 'v0.18.29'); + assert.equal(ok.version, '0.18.29'); assert.equal(ok.recorded_fingerprint, ok.current_fingerprint, '剛戳完版就該對得上'); assert.deepEqual(ok.changed, []);