diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b399a4..e4670d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,12 @@ > 那條臍帶讓 `collector/` **沒有資格被搬成獨立 repo**,也讓「源碼→產出物」多了一道要記得的扭曲。 > 現在它與 `DAEMON_LINE`、`daemon-version.py` 住在同一棵樹底下,`collector/` 自己就算得出自己的版本。 > -> 🔴 **雲端(Arcrun 引擎 1.4.x)的版本說明不在這裡**,它留在 -> `docs-site/src/content/docs/help/changelog.md`——兩條版本線本來就是兩個產品, -> 混在一個檔裡是 D95 要拆掉的那種扭曲。 +> 🔴 **雲端(Arcrun 引擎 1.4.x)的版本說明不在這裡**,它住在 repo 根的 `CHANGELOG.md` +> (2026-08-17 從 docs-site 搬去,同一天 leo 說「這個頁面刪除」)—— +> 兩條版本線本來就是兩個產品,混在一個檔裡是 D95 要拆掉的那種扭曲。 +> +> 📌 **兩份都不是網頁,是出貨原稿。** 使用者讀的是 +> [GitHub 版本發佈](https://github.com/youlinhsieh/arcrun-rag/releases),由 `installer/scripts/github-release.mjs` 拿這兩份檔的段落產生。 ## 怎麼出新版(不要手打版本號) diff --git a/cmd/arcrun-app/daemon-notes.mjs b/cmd/arcrun-app/daemon-notes.mjs index fc306a0..027df39 100644 --- a/cmd/arcrun-app/daemon-notes.mjs +++ b/cmd/arcrun-app/daemon-notes.mjs @@ -49,70 +49,6 @@ export function stripMarkdown(s) { .trim(); } -/** - * 「已發佈」的版本段落長這樣:`## v0.18.29(2026-08-16)`。 - * 待發佈的那一段標題是「下一版(未發佈)」,**不符合這個形狀** ⇒ 天然不會被投影出去。 - * 這是刻意的:投影器不必另外認一次「未發佈」,少一條要記得的規則。 - */ -const RELEASED_HEADING = /^##\s+(v\d+\.\d+\.\d+)(?=\D|$)/; - -/** - * 把 changelog 切成「只剩已發佈段落」的 markdown。**給文件站投影用。** - * - * ── 為什麼這件事住在 collector/(2026-08-18,D95 第三輪)───────────────── - * 文件站要在建置時把桌面版的更新內容接回 `/docs/help/changelog/` 那一頁 - * (網址不變、使用者看到的東西不變)。**接的方式必須是「讀本檔」而不是「複製一份」** - * ——第一輪把這條線搬進 `collector/CHANGELOG.md` 就是為了拔掉「同一份 changelog 存兩地」。 - * - * 而「哪些段落算已發佈、preamble 要丟掉」是 **changelog 自己的格式知識**, - * 不是文件站的知識 ⇒ 解析住在 daemon 這棵樹裡,文件站只負責把結果貼上去。 - * 方向仍然單向:**根(docs-site)往內伸手拿,collector 不往外伸手。** - * - * 丟掉的:檔頭那段給維護者看的說明(含「怎麼出新版」與那個 `<二級標題>` 範例)、 - * 以及任何還沒戳版號的段落。留下的:每一個 `## vX.Y.Z(日期)` 段的原文,一字不改。 - * - * @param {string} changelogText changelog 全文 - * @returns {{markdown: string, versions: string[]}} markdown=只剩已發佈段落;versions=由新到舊 - */ -export function releasedSections(changelogText) { - const lines = String(changelogText).split('\n'); - const kept = []; - const versions = []; - let inside = false; - for (const l of lines) { - // 只有二級標題會切換「現在在不在一個已發佈段落裡」。 - // `### 小標` 不會(`^##\s` 要求第三個字是空白),所以段落內的結構完整保留。 - if (/^##\s/.test(l)) { - const m = l.match(RELEASED_HEADING); - inside = Boolean(m); - if (m) versions.push(m[1]); - } - if (inside) kept.push(l); - } - return { markdown: kept.join('\n').trim(), versions }; -} - -/** - * 同 `releasedSections()`,但直接讀檔,且**問不出東西就 throw**。 - * - * 🔴 為什麼是 throw 不是回空字串:這支的呼叫端是文件站的建置。 - * 回空字串=建置成功、頁面少了整條桌面版版本歷史、**沒有任何人會發現**—— - * 那正是 D95 第二輪抓到的那個形狀(出貨線問錯檔案就整站安靜跳過)。 - * 壞掉要當場停,不要安靜地產出一個少一半的頁面。 - */ -export function releasedSectionsFromFile(changelogPath = CHANGELOG_PATH) { - if (!existsSync(changelogPath)) { - throw new Error(`找不到 daemon 的版本說明檔:${changelogPath}(單一真相源=collector/CHANGELOG.md)`); - } - const r = releasedSections(readFileSync(changelogPath, 'utf8')); - if (!r.versions.length) { - throw new Error( - `${changelogPath} 裡沒有任何**已發佈**的版本段(\`## vX.Y.Z(日期)\`)。\n` + - ' → 若只有「下一版(未發佈)」,先跑 `daemon-version.py --stamp` 戳成正式版號。'); - } - return { ...r, path: changelogPath }; -} - /** * 從 changelog 取某一版的「一句話摘要」清單。 * 規則:只認**頂層條目**(行首 `- `)的第一個粗體片段——那就是該條的標題。 diff --git a/cmd/arcrun-app/daemon-notes.test.mjs b/cmd/arcrun-app/daemon-notes.test.mjs deleted file mode 100644 index 7614786..0000000 --- a/cmd/arcrun-app/daemon-notes.test.mjs +++ /dev/null @@ -1,80 +0,0 @@ -/** - * daemon-notes.test.mjs — `releasedSections()` 的演練(D95 第三輪) - * - * 這支存在的理由:`releasedSections()` 的產出**會直接變成使用者讀的那一頁** - * (`rag.arcrun.dev/docs/help/changelog/`)。它多切一段、少切一段、或把給維護者看的 - * preamble 漏出去,都是使用者當場看得到的錯,而建置不會報任何錯。 - * - * 跑法:node --test collector/cmd/arcrun-app/daemon-notes.test.mjs - */ -import { test } from 'node:test'; -import assert from 'node:assert/strict'; -import { readFileSync } from 'node:fs'; -import { releasedSections, releasedSectionsFromFile, CHANGELOG_PATH } from './daemon-notes.mjs'; - -const FIXTURE = [ - '# Arcrun 桌面版(daemon)版本說明', - '', - '> 給維護者看的說明,不該出現在網站上。', - '', - '## 怎麼出新版(不要手打版本號)', - '', - '在本檔最上面加一個二級標題:', - '', - ' <二級標題> 下一版(未發佈)', - '', - '---', - '', - '## 下一版(未發佈)', - '', - '- 🔑 **還沒戳版號的東西**:不該出現在網站上。', - '', - '## v0.18.29(2026-08-16)', - '', - '**建議更新**', - '', - '- 🔑 **甲**:內文甲。', - '', - '### 小標題也要留著', - '', - '- 乙', - '', - '## v0.18.28(2026-08-16)', - '', - '- 丙', - '', -].join('\n'); - -test('只留已發佈段落:preamble 與「下一版(未發佈)」都不會被投影出去', () => { - const { markdown, versions } = releasedSections(FIXTURE); - assert.deepEqual(versions, ['v0.18.29', 'v0.18.28']); - for (const forbidden of ['給維護者看的說明', '怎麼出新版', '<二級標題>', '下一版(未發佈)', '還沒戳版號']) { - assert.ok(!markdown.includes(forbidden), `不該出現在使用者眼前:${forbidden}`); - } - assert.ok(markdown.startsWith('## v0.18.29(2026-08-16)'), '第一行就該是最新的已發佈版本'); -}); - -test('段落內文原樣保留,`###` 小標題不會被當成段落結束', () => { - const { markdown } = releasedSections(FIXTURE); - assert.ok(markdown.includes('### 小標題也要留著')); - assert.ok(markdown.includes('- 乙'), '`###` 之後的內容仍屬於同一個版本段'); - assert.ok(markdown.includes('**建議更新**') && markdown.includes('- 丙')); -}); - -test('一段都沒有就 throw——不准安靜回空字串(那會建出一頁少一半的東西)', () => { - assert.throws( - () => releasedSectionsFromFile('/nonexistent/CHANGELOG.md'), - /找不到 daemon 的版本說明檔/); - // 只有未發佈段落時同樣要停 - const onlyUnreleased = '# 標題\n\n## 下一版(未發佈)\n\n- 甲\n'; - assert.deepEqual(releasedSections(onlyUnreleased).versions, []); -}); - -test('對真的 collector/CHANGELOG.md 跑一次:段數=檔案裡 `## vX.Y.Z` 的行數', () => { - const raw = readFileSync(CHANGELOG_PATH, 'utf8'); - const expected = raw.split('\n').filter((l) => /^##\s+v\d+\.\d+\.\d+/.test(l)).length; - const { versions, markdown } = releasedSectionsFromFile(); - assert.equal(versions.length, expected); - assert.ok(expected > 0, '真檔裡至少要有一個已發佈版本'); - assert.ok(!markdown.includes('單一真相源'), '檔頭那段維護者說明不該被投影出去'); -});