news 2026/9/13 8:53:21

Tolaria 日历 Semver 版本体系:Alpha 与 Stable 双通道的版本号设计与单调性保护

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Tolaria 日历 Semver 版本体系:Alpha 与 Stable 双通道的版本号设计与单调性保护

Tolaria 日历 Semver 版本体系:Alpha 与 Stable 双通道的版本号设计与单调性保护

【免费下载链接】tolariaDesktop app to manage markdown knowledge bases项目地址: https://gitcode.com/GitHub_Trending/to/tolaria

本篇围绕 Tolaria 仓库中的架构决策记录 ADR-0066 展开,讲解 Tolaria 如何用"日历 Semver"(Calendar Semver)统一 Alpha 与 Stable 两条发布通道的版本号:Stable 以stable-vYYYY.M.D标签晋级、Alpha 随每次推送main自动生成YYYY.M.D-alpha.N。读完本文,你将掌握该版本模型的设计动机、"同日晋级单调性保护"的完整规则、scripts/release-version.mjs 中的核心算法实现,以及它与发布工作流、应用内更新器之间的衔接方式。

背景:为什么 ADR-0057 的版本方案不再够用

Tolaria 的发布模型由前序决策 ADR-0057 奠定:应用只保留stablealpha两条更新通道,main分支的每次推送都会发布一个 Alpha 预发布构建,而 Stable 版本通过手动推送标签晋级。但 ADR-0057 给 Alpha 构建分配的版本号是"下一个 Stable 补丁版"的预发布形式(例如在 Stable1.2.3之后生成1.2.4-alpha.202604122135.7)。这种方式虽然保证了 Semver 排序安全,却与当时已敲定的产品命名约定脱节。

ADR-0066 在 Context 一节中列出了新的产品命名要求,这也是整个版本模型重构的出发点:

通道用户可见的显示版本技术版本号(写入包/更新清单)
AlphaAlpha YYYY.M.D.NYYYY.M.D-alpha.N
StableYYYY.M.DYYYY.M.D

其中N是同一日历日期内的 Alpha 序号。命名变干净了,但引出了一个必须解决的技术矛盾:纯同日期的日历 Alpha 版本,在同一天被晋级为 Stable 后会比 Stable 更"旧"。例如当天已发布2026.8.2的 Stable,如果当天的 Alpha 仍然编号为2026.8.2-alpha.N,由于 Semver 规定预发布版本低于对应正式版,更新器就会判定这些 Alpha 构建不比当前版本新,用户从 Stable 切回 Alpha 后将永远收不到更新。因此 ADR-0066 明确指出:除了更干净的显示字符串,工作流还需要一个单调性保护(monotonicity guard)

核心决策:两条通道、日历 Semver 与次日保护

ADR-0066 的 Decision 部分给出的规则可以归纳为三条:

  1. Tolaria 恰好保留两条更新通道(stablealpha),且两者统一改用日历 Semver 编号。Stable 晋级使用stable-vYYYY.M.D标签,技术版本号直接盖章为YYYY.M.D
  2. 每次推送main都会发布一个 Alpha 构建,技术版本号为YYYY.M.D-alpha.N,显示标签为Alpha YYYY.M.D.N
  3. 同日晋级保护:如果最新的 Stable 标签已经使用了当前 UTC 日历日期,Alpha 工作流会先推进到下一个日历日再分配-alpha.N。这样即使同一天刚晋级过 Stable,当天后续发布的 Alpha 在 Semver 意义上依然比最新 Stable 新。

用具体例子理解第三条规则:假设 UTC 当天是2026-08-02,且已经存在同日的 Stable 标签v2026-08-02(技术版本2026.8.2)。此时再推送main触发 Alpha 构建,版本计算不会生成2026.8.2-alpha.N,而是把发布日期推进到2026-08-03,产出技术版本2026.8.3-alpha.1、显示版本Alpha 2026.8.3.1。在 Semver 比较中2026.8.3-alpha.1 > 2026.8.2,更新顺序保持单调。

这一规则在 scripts/release-version.mjs 的computeAlphaRelease函数中有直接对应(L87-L90):

const latestValidStable = latestDate(stableDates.filter((value) => value <= todayDate)) const regularDate = latestValidStable && compareDates(latestValidStable, todayDate) === 0 ? nextDay(todayDate) : todayDate

即:取"不晚于今天"的最新 Stable 日期,若它与今天相同,则 Alpha 的发布日期取nextDay(todayDate),否则取今天。

实现解析:scripts/release-version.mjs 的版本计算模块

日历版本的全部计算逻辑集中在独立模块 scripts/release-version.mjs 中,并有配套测试 scripts/release-version.test.mjs。docs/ABSTRACTIONS.md 将该模块描述为"受 CircleCI 权威发布流程与兼容性 GitHub Actions 发布工作流共享的、经过测试的日历版本策略"。下面按模块内的关键构件逐一说明。

标签格式与正则约束

模块顶部定义了三种标签格式(L5-L7),它们界定了整个体系可识别的版本标签:

const ALPHA_TAG_PATTERN = /^alpha-v(\d{4})\.(\d{1,2})\.(\d{1,2})-alpha\.(\d+)$/ const CALENDAR_STABLE_PATTERN = /^v(\d{4})-(\d{2})-(\d{2})$/ const LEGACY_STABLE_PATTERN = /^stable-v(\d{4})\.(\d{1,2})\.(\d{1,2})$/
  • alpha-vYYYY.M.D-alpha.NNNN:Alpha 构建打回的 git 标签,序号在标签中零填充为 4 位;
  • vYYYY-MM-DD:日历风格的 Stable 标签;
  • stable-vYYYY.M.D:ADR-0057/0066 时代沿用的 Stable 晋级标签,作为遗留格式继续被解析。

值得注意的一个实现细节:日期解析后还会做回读校验——parseDateParts将年、月、日组装成 UTC 日期后再逐项比对(L11-L18),2026-02-30这类不存在的日历日期会直接抛出Invalid calendar date错误,而不是静默地滚动到下一月。

Alpha 版本计算:computeAlphaRelease

computeAlphaRelease是 Alpha 通道的主入口(L70-L102)。它接收四组输入:全部 Alpha 标签、全部 Stable 标签、当前指向 HEAD 的标签、以及"今天"的 UTC 日期,返回一个包含四个字段的结果:

return { channel: 'alpha', displayVersion: `Alpha ${calendarCore(needsRecoveryBridge ? todayDate : releaseDate)}.${displaySequence}`, tag: `alpha-v${core}-alpha.${String(sequence).padStart(4, '0')}`, version: `${core}-alpha.${sequence}`, }

其中calendarCore把日期格式化为无前导零的YYYY.M.D(L55-L57),这正是显示规范中Alpha YYYY.M.D.N的由来。序列号N的统计口径是同一日历核心日期下的 Alpha 标签数量加一(L93):

const sequence = alphaReleases.filter(({ date }) => calendarCore(date) === core).length + 1

也就是说,序号按天重置:8 月 3 日的第一次推送得到2026.8.3-alpha.1,当天第二次推送得到2026.8.3-alpha.2,而 8 月 4 日重新从-alpha.1开始。这一口径与 ADR-0066 后果一节中"Alpha 序号作用域限定于某个日历核心日期,且与更新器清单保持兼容"的表述一致。

此外,当 HEAD 上已经带有 Alpha 标签时(例如重跑构建),函数会直接依据该标签重建版本号(releaseFromTag,L59-L68),保证同一构建的重复发布不会推进序列号。

Stable 版本计算:computeStableRelease

computeStableRelease(L104-L114)逻辑更直接:解析标签内嵌日期,若标签日期晚于当前 UTC 日期则立即报错(Stable tag ${tag} cannot be later than the current UTC date),防止手误打出的未来日期标签污染发布元数据;版本号取calendarCore(tagDate),显示版本则优先保留标签原文(以v开头的日历标签直接展示标签,遗留的stable-vYYYY.M.D标签展示为YYYY.M.D)。

面向 CI 的环境变量输出

模块通过formatReleaseEnv把计算结果序列化为两种格式(L145-L150):shell 格式使用大写变量(VERSIONDISPLAY_VERSIONTAGCHANNELSKIP_RELEASE),github 格式使用小写下划线变量(version=…display_version=…等)以便直接追加到GITHUB_OUTPUT。命令行接口为:

node scripts/release-version.mjs alpha|stable [shell|github]

其中RELEASE_TODAY环境变量可覆盖"今天"的取值(默认取当前 UTC 日期,L188),这让发布逻辑可以完全离线、确定性地测试与演练;Stable 通道的标签则通过RELEASE_TAGCIRCLE_TAG_VALUE传入(L176)。

Alpha 通道在真实 git 仓库中运行时,alphaReleaseFromGit(L156-L173)还会做两件事:其一,通过git diff-tree检查本次提交的变更文件,如果全部变更都属于site/.circleci/.github/workflows/README.md,则设置SKIP_RELEASE=true,让纯文档类变更跳过构建发布;其二,通过git tag --points-at HEAD找出指向当前 HEAD 的 Alpha 标签,实现前述的重跑幂等。

测试用例如何锁定"同日保护"语义

scripts/release-version.test.mjs 使用 Node 内置node:test运行,并被纳入仓库的总测试命令——package.json 的test脚本为vitest run && node --test scripts/release-version.test.mjs。关键用例如下:

同日 Stable 保护(L7-L16):给定today = 2026-08-02且存在同日 Stable 标签v2026-08-02,断言计算结果为:

expected: { channel: 'alpha', displayVersion: 'Alpha 2026.8.3.1', tag: 'alpha-v2026.8.3-alpha.0001', version: '2026.8.3-alpha.1', }

这一用例把 ADR 中的文字规则固化为可回归的断言:显示版本与技术版本都落在"次日"核心日期上,序号为 1,标签序号零填充为0001

未来 Stable 标签拒绝(L73-L78):computeStableRelease({ tag: 'v2027-07-31', today: '2026-08-02' })必须抛出异常,错误信息包含cannot be later than the current UTC date 2026-08-02

GitHub 输出格式(L50-L71):断言 github 格式的输出逐行等于version=2026.8.2-alpha.1display_version=Alpha 2026.8.2.1tag=alpha-v2026.8.2-alpha.0001channel=alphaskip_release=false,锁定了 CI 消费该模块时的契约。

与发布流水线的集成

Alpha 通道由 .github/workflows/release.yml 驱动。该工作流在推送到main时触发(并对.husky/**site/**等路径设置了paths-ignore),其第一阶段作业Compute alpha version的注释明确写着"Alpha builds use calendar semver and stay newer than the latest stable tag",执行步骤为:

node --test scripts/release-version.test.mjs node scripts/release-version.mjs alpha github > version.env cat version.env >> "$GITHUB_OUTPUT"

先跑版本计算模块的测试,再运行模块本体,并把version.env中的versiondisplay_versiontag输出为作业级 outputs,供后续构建阶段消费;计算出的版本号同时写入GITHUB_STEP_SUMMARY,在 CI 运行页面上可见。Stable 通道则由 .github/workflows/release-stable.yml 仅在stable-v*标签触发时运行,与 ADR-0057 中"release-stable.yml只从stable-v*标签发布"的描述一致。

从源码结构看,该仓库的发布编排存在双轨:GitHub Actions 的上述工作流提供兼容入口,而权威发布编排由 CircleCI 承担(ADR-0172)。两轨共享同一个scripts/release-version.mjs计算核心,因此无论哪条流水线触发,版本的判定规则都是同一份经过测试的策略代码。

与更新器的衔接:技术版本与显示版本分离

ADR-0066 的后果一节特别强调:发布工作流现在同时计算技术版本显示版本,而所有面向用户的版本表面会把技术预发布噪声(-alpha.N)剥离为干净标签。这一"双版本"设计在更新器侧得到印证:

  • src-tauri/src/app_updater.rs 根据设置中的release_channel选择alpha/latest.jsonstable/latest.json两个更新端点,其测试断言两个端点分别为https://refactoringhq.github.io/tolaria/alpha/latest.jsonhttps://refactoringhq.github.io/tolaria/stable/latest.json(L349-L356);
  • release_channel保留为应用设置,默认值为 Stable,只有显式的alpha会被持久化,遗留或非法的通道值一律回退到 Stable(ADR-0057 的既有约束,ADR-0066 未改变该行为)。

也就是说:清单与包体里携带的2026.8.3-alpha.1供 Semver 比较器做单调性判断,而用户界面、发布页展示的是Alpha 2026.8.3.1。两者由同一个计算结果一次性派生,避免了命名漂移。

方案权衡与后续演进

ADR-0066 在 Options considered 一节中列出了三个候选方案:

  • 日历 Semver + 次日保护(被采纳):既符合约定的产品命名,又通过单调性保护维持跨通道切换时的更新器排序;
  • 无保护的日历 Semver:显示模型最简单,但同日晋级后 Alpha 会变成 Semver 意义上的"旧版本";
  • 沿用 ADR-0057 的"下一补丁预发布"编号:Semver 安全,但与约定命名和产品表面的日历化版本不符。

从后续文档可以看到该体系仍在持续加固:ADR-0173(2026-08-02,声明取代 ADR-0066)针对一次真实事故——手工误打的v2027-07-31未来日期标签曾通过 Stable 流程并"毒化"了 Alpha 排序——补充了"未来日期 Stable 标签一律 fail-closed 拒绝"与一个一次性的恢复桥接(发布技术版本比污染核心日期高一天的桥接构建,显示标签用真实 UTC 日期配序号 0)。当前 scripts/release-version.mjs 中可见的recoveryBridgeDate计算、STABLE_BRIDGE_TAG一次性操作员例外,以及测试文件里的monotonic poisoned-version bridge等用例,正是 ADR-0173 对 ADR-0066 基线的叠加。换言之,ADR-0066 建立的"日历核心日期 + 序号 + 次日保护"骨架至今未变,变的是围绕它的故障恢复能力。

参考文件索引

  • 决策文档:docs/adr/0066-calendar-semver-versioning-for-alpha-and-stable-releases.md、docs/adr/0057-alpha-stable-release-channels-and-beta-cohorts.md、docs/adr/0173-future-calendar-version-recovery.md
  • 版本计算与测试:scripts/release-version.mjs、scripts/release-version.test.mjs
  • 发布工作流:.github/workflows/release.yml、.github/workflows/release-stable.yml
  • 更新器实现:src-tauri/src/app_updater.rs
  • 抽象说明:docs/ABSTRACTIONS.md、package.json

【免费下载链接】tolariaDesktop app to manage markdown knowledge bases项目地址: https://gitcode.com/GitHub_Trending/to/tolaria

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 8:53:20

二阶锥松弛在微电网最优潮流计算中的应用与Matlab实现

1. 项目背景与核心价值微电网作为分布式电源接入配电网的重要载体&#xff0c;其灵活性调节能力直接影响着配电网运行的经济性和安全性。传统配电网最优潮流&#xff08;OPF&#xff09;计算在考虑分布式电源时&#xff0c;常因非凸非线性特性导致求解困难。二阶锥松弛&#xf…

作者头像 李华
网站建设 2026/9/13 8:49:21

三道经典测试题,快速分辨GPT-4与GPT-3.5套壳接口

告诉你个尴尬事。前阵子有个朋友找我&#xff0c;说他们团队买了一家第三方AI接口&#xff0c;宣传清清楚楚写着“GPT-4”&#xff0c;结果做出来的产品在复杂逻辑问答上一塌糊涂&#xff0c;用户投诉不断。我让他把接口的返回参数亮出来&#xff0c;结果model字段里赫然写着“…

作者头像 李华
网站建设 2026/9/13 8:48:47

GitHub到Gitea仓库迁移实战:含批量脚本与踩坑避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 8:48:01

如何用 LeRobot 在 MetaWorld MT50 基准上评估策略?

如何用 LeRobot 在 MetaWorld MT50 基准上评估策略&#xff1f; 【免费下载链接】lerobot &#x1f917; LeRobot: Making AI for Robotics more accessible with end-to-end learning 项目地址: https://gitcode.com/GitHub_Trending/le/lerobot 你手上已经有一个训练好…

作者头像 李华
网站建设 2026/9/13 8:46:51

IDE选型本质是抽象层级匹配:程序员生存装备图谱

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华