<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>版本化 · XB 现场笔记</title><link>https://www.xbcatm.com/zh/tags/%E7%89%88%E6%9C%AC%E5%8C%96/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/tags/%E7%89%88%E6%9C%AC%E5%8C%96/index.xml" rel="self" type="application/rss+xml"/><item><title>语义化版本让兼容性承诺更明确</title><link>https://www.xbcatm.com/zh/posts/semantic-versioning-makes-compatibility-explicit/</link><pubDate>Sat, 13 Feb 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/posts/semantic-versioning-makes-compatibility-explicit/</guid><description>版本号表达兼容性判断；公共契约与包管理器的范围规则仍需分别验证。</description><content:encoded>&lt;p&gt;依赖版本有价值的前提，是发布者与使用者对公共契约有共同认识。语义化版本为变化提供一套表达：从 1.0.0 开始，不兼容的公共接口变化需要提升主版本，兼容的功能增加需要提升次版本，兼容的问题修复需要提升补丁版本。数字归纳的是发布者对这一版的判断，并不会自行检查实现，也不能保证应用升级之后必然正确运行。理解承诺边界，比只记住三个数字更有用。&lt;/p&gt;
&lt;h2 id="先声明兼容性覆盖哪些内容"&gt;先声明兼容性覆盖哪些内容&lt;/h2&gt;
&lt;p&gt;公共接口可以包括导出的函数、正式说明的命令参数、配置格式、响应字段和明确约定的错误行为。边界应写得足够清楚，才能判断一次变化是否违约。保持契约不变的内部重构，与删除已经承诺支持的参数，不属于同一类变化。修正原先违反文档契约的行为，可能仍是兼容的缺陷修复，即便有人曾经观察并依赖那个错误表现。仍需在变更说明中解释，并验证受影响的调用方。&lt;/p&gt;
&lt;p&gt;语义化版本规范要求：把公共功能标记为废弃时，应提升次版本；之后真正移除功能，则需要按不兼容变化处理。已经发布的某个版本，其内容不应继续变化，修正应通过另一个新版本交付。在相同版本标签背后替换文件，会破坏追溯：两个依赖声明完全相同的安装过程，可能得到不同的字节，排查问题时也无法再靠版本号明确区分。&lt;/p&gt;
&lt;p&gt;主版本为零表示初始开发阶段，公共接口不应被视为稳定。维护者可以为这一阶段声明额外约定，但使用者不能默认所有零主版本更新都兼容。预发布版本的优先级低于对应的正式版本；加号之后的构建元数据不参与优先级比较。因此，两个版本在排序意义上相等，不意味着它们的包内容、来源或文件摘要相同。兼容性、选择顺序与产物身份是不同问题。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;标错的次版本即使满足依赖范围，也可能破坏已声明的契约。标号正确的发布，也可能暴露应用对内部实现细节的依赖。升级时仍应读变更说明，运行有代表性的集成验证；需要重复解析依赖时保留锁文件。锁文件记录当时选择了哪些版本，不能替这些版本证明行为正确，更不是来源真实性的证明。版本范围允许安装，只说明解析条件满足了。&lt;/p&gt;
&lt;p&gt;包管理器的范围语法又是另一层规则。语义化版本规范定义版本及优先级，具体工具决定约束怎样筛选候选。在 node-semver 中，插入符通常允许最左非零分量右侧的变化，所以 &lt;code&gt;^0.2.3&lt;/code&gt; 不接受 &lt;code&gt;0.3.0&lt;/code&gt;，&lt;code&gt;^0.0.3&lt;/code&gt; 也不接受 &lt;code&gt;0.0.4&lt;/code&gt;。三个分量完整的波浪号范围，通常只允许同一次版本线内的补丁更新。不能把所有插入符都理解成“只要主版本不变就行”。&lt;/p&gt;
&lt;p&gt;预发布筛选还有附加规则。宽泛的稳定版本范围，不会自动接纳数值更高的测试版；明确允许某个预发布三元组后，才可能接纳该三元组后续的预发布版本。应验证实际解析器，而不是用普通字符串排序推断升级关系，也不要把数值更大直接等同于可被当前范围选择。不同生态的约束语法不能仅凭相似外观相互套用。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;本轮安装尝试在解析器断言执行之前以失败状态退出；这里没有把现有其他版本库的行为算成该固定版本示例的通过证据。&lt;/p&gt;
&lt;p&gt;将代码保存为 &lt;code&gt;semver.py&lt;/code&gt;，在已有 Node 和 npm 的环境执行 &lt;code&gt;python3 semver.py&lt;/code&gt;。实验需要访问公共 npm 仓库。脚本在临时目录安装 &lt;code&gt;semver@7.7.2&lt;/code&gt;，隔离配置和缓存，关闭包安装脚本，再调用这个库真实的比较函数。它不会修改当前项目的依赖声明或锁文件；结束时临时安装目录随上下文清理。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;tempfile&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TemporaryDirectory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;TemporaryDirectory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;user_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;user.npmrc&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;global_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;global.npmrc&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;user_config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;global_config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;PATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;PATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;LC_ALL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;C&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;NPM_CONFIG_USERCONFIG&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_config&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;NPM_CONFIG_GLOBALCONFIG&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;global_config&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;NPM_CONFIG_CACHE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;cache&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;npm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;install&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--ignore-scripts&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--no-audit&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--no-fund&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;--package-lock=false&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--registry=https://registry.npmjs.org&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;semver@7.7.2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;const s = require(&amp;#39;semver&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;const assert = require(&amp;#39;node:assert/strict&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;const cases = [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; [&amp;#39;1.9.0&amp;#39;, &amp;#39;^1.2.3&amp;#39;, true], [&amp;#39;2.0.0&amp;#39;, &amp;#39;^1.2.3&amp;#39;, false],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; [&amp;#39;1.2.9&amp;#39;, &amp;#39;~1.2.3&amp;#39;, true], [&amp;#39;1.3.0&amp;#39;, &amp;#39;~1.2.3&amp;#39;, false],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; [&amp;#39;0.2.9&amp;#39;, &amp;#39;^0.2.3&amp;#39;, true], [&amp;#39;0.3.0&amp;#39;, &amp;#39;^0.2.3&amp;#39;, false],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; [&amp;#39;0.0.4&amp;#39;, &amp;#39;^0.0.3&amp;#39;, false], [&amp;#39;1.3.0-beta.1&amp;#39;, &amp;#39;^1.2.3&amp;#39;, false],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; [&amp;#39;1.3.0-beta.2&amp;#39;, &amp;#39;^1.3.0-beta.1&amp;#39;, true]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;].map(([version, range, expected]) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; const accepted = s.satisfies(version, range);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; assert.equal(accepted, expected);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; return {version, range, accepted};
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;assert.equal(s.compare(&amp;#39;1.2.3+build.1&amp;#39;, &amp;#39;1.2.3+build.2&amp;#39;), 0);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;assert.equal(s.lt(&amp;#39;1.3.0-beta.2&amp;#39;, &amp;#39;1.3.0&amp;#39;), true);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;assert.equal(s.valid(&amp;#39;1.02.3&amp;#39;), null);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;console.log(JSON.stringify({library: require(&amp;#39;semver/package.json&amp;#39;).version, cases,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; metadata_precedence_equal: true, prerelease_before_release: true, leading_zero_rejected: true}));
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;node&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-e&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;九个范围案例应当全部符合断言：&lt;code&gt;^1.2.3&lt;/code&gt; 接受 &lt;code&gt;1.9.0&lt;/code&gt;，拒绝 &lt;code&gt;2.0.0&lt;/code&gt;；波浪号拒绝下一次版本；两个零主版本范围更加收敛。稳定范围拒绝 &lt;code&gt;1.3.0-beta.1&lt;/code&gt;，明确指定预发布的范围则接受 &lt;code&gt;1.3.0-beta.2&lt;/code&gt;。输出保留版本、范围与布尔结果，便于逐项看清究竟是哪条条件在起作用。&lt;/p&gt;
&lt;p&gt;其余断言准备检查两份构建元数据的优先级相等、测试版排在相应正式版之前，以及 &lt;code&gt;1.02.3&lt;/code&gt; 这种带前导零的版本无效。完整运行通过后也只覆盖解析与选择规则，不验证某个真实依赖升级的兼容性；本轮没有执行到这些断言。决定版本提升幅度时，仍需把公共接口的实际变化和受影响调用方放在一起审阅。版本号负责传达判断，变更说明与测试负责让别人能够核对这个判断。&lt;/p&gt;
&lt;h2 id="延伸阅读"&gt;延伸阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.xbcatm.com/zh/notes/a-release-checklist-for-a-static-site/"&gt;静态网站的发布检查清单&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://semver.org/spec/v2.0.0.html"&gt;Semantic Versioning 2.0.0&lt;/a&gt;，公共接口承诺、版本优先级和初始开发规则，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/npm/node-semver/tree/v7.7.2"&gt;npm: node-semver&lt;/a&gt;，插入符、波浪号及预发布范围的实际语义，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>