HarmonyOS 7 + semver + ohpm:插件能力协商的预发布边界与降级矩阵【鸿蒙心迹】
依赖安装成功,只能说明包被解析并落到了工程里。它不等于“当前版本可以被业务启用”,更不等于“预发布版本可以按稳定版本的规则自动放行”。
这篇文章把一个很小的检查脚本扩成可审计的能力门禁。Demo 名为 PluginRangeLab,诊断页是 CapabilityAuditPage,任务编号 SEMVER-CAP-0212。21:36,宿主声明稳定通道合同 >=2.8.0 <3.0.0,实际检测到 vision-bridge@3.0.0-beta.4。版本比 2.8.0 新,却没有进入稳定通道;只有显式切到 canary 通道,使用 >=3.0.0-beta.2 <3.0.0,才进入下一步能力协商。
文中结果是工程样例的确定性输出,不代表 ohpm 会自动执行这些业务策略。ohpm 负责 HarmonyOS 三方包的发布、安装和依赖管理;本文的通道、能力清单与降级矩阵属于项目自建门禁。

一、版本“更大”不代表范围“满足”
很多版本判断从字符串比较开始:3.0.0-beta.4 > 2.8.0,于是代码放行。这个结论跳过了 SemVer 对预发布标记的特殊语义。
node-semver 的官方说明很明确:带 prerelease 标记的版本,默认只有在比较器集合里存在相同 major/minor/patch 元组的预发布比较器时,才可能满足该集合。这个规则是有意设计的,因为 alpha、beta、rc 往往更新快,也可能包含未稳定的破坏性变化。
因此 3.0.0-beta.4 不满足 >=2.8.0 <3.0.0。<3.0.0 在排序上确实覆盖 beta,但默认范围匹配仍会排除没有被显式选择的预发布版本。若项目确实要试用,应该写出带预发布元组的 canary 范围,而不是把 includePrerelease: true 当成全局万能开关。
Demo 的基础事实如下:
- 宿主:
CatalogHost@5.4.0。 - 插件:
vision-bridge@3.0.0-beta.4。 - 稳定通道范围:
>=2.8.0 <3.0.0,结果false。 - canary 通道范围:
>=3.0.0-beta.2 <3.0.0,结果true。 - 预发布标记:
['beta', 4]。 - 必需能力:
search.preview,匹配 1/1。 - 可选能力:
search.batch,缺失 1。 - 最终决定:
DEGRADED_READY,只在 canary 通道启用预览,批处理入口隐藏。
我刻意把“版本范围”和“能力集合”分成两道门。版本范围回答团队是否接受这一发布线;能力集合回答当前二进制究竟能做什么。把两者混在一个 if 里,会让版本号承担它并未承诺的业务语义。
二、先严格解析,再决定通道
版本来自清单、生成报告或外部插件描述时,最危险的做法是先 coerce()。node-semver 的 coerce 会尽力从任意字符串中提取可用数字,例如可能把包含说明文字的内容转成三段版本。它适合清洗非合同输入,不适合发布门禁,因为修复动作会抹去原始错误。
门禁应该使用严格 valid() 和 validRange();不合法就报告原值,而不是猜测作者的意思。
这段代码解决版本字符串、范围和预发布标记的严格判定。
import * as semver from 'semver';
interface VersionDecision {
rawVersion: string;
normalized: string | null;
channel: 'stable' | 'canary';
range: string;
prerelease: ReadonlyArray<string | number> | null;
satisfies: boolean;
reason: string;
}
function checkVersion(rawVersion: string,
channel: 'stable' | 'canary'): VersionDecision {
const stableRange = '>=2.8.0 <3.0.0';
const canaryRange = '>=3.0.0-beta.2 <3.0.0';
const range = channel === 'stable' ? stableRange : canaryRange;
const normalized = semver.valid(rawVersion);
const validRange = semver.validRange(range);
if (normalized === null || validRange === null) {
return {
rawVersion, normalized: null, channel, range,
prerelease: null, satisfies: false, reason: 'INVALID_CONTRACT'
};
}
const pre = semver.prerelease(normalized);
const accepted = semver.satisfies(normalized, validRange);
return {
rawVersion, normalized, channel, range,
prerelease: pre, satisfies: accepted,
reason: accepted ? 'RANGE_ACCEPTED' :
(pre === null ? 'OUTSIDE_RANGE' : 'PRERELEASE_NOT_OPTED_IN')
};
}
在稳定通道调用 checkVersion('3.0.0-beta.4', 'stable'),得到 PRERELEASE_NOT_OPTED_IN。切到 canary 后,范围显式写入 3.0.0-beta.2 这一元组,结果才变为 RANGE_ACCEPTED。
为什么不直接给 satisfies 传 { includePrerelease: true }?因为它会扩大整个范围的候选集合,审查者很难从一条通用范围看出团队到底接受哪条预发布线。显式 canary 范围更窄,也能作为代码评审中的可见意图。
还有一个细节:prerelease() 返回 null 表示稳定版本,返回数组表示预发布标记。不要只用字符串包含 -beta 判断。rc、自定义标记以及数字标识符都会让手写规则迅速失控。
三、版本门通过后,能力门才开始
版本符合范围并不证明插件实现了宿主需要的全部能力。特别是 beta 版本,功能可能分批交付。本文给插件清单增加独立的 capabilities 数组,不从版本号推断功能。
清单样例放在项目工具目录,由 Hvigor 前置任务或独立 Node.js 命令读取:
清单的关键值为:name=vision-bridge、version=3.0.0-beta.4,能力集合包含 search.preview、search.basic 与 telemetry.trace。这里故意没有 search.batch,用来验证可选能力缺失后的降级分支。
宿主策略把能力分成 required 和 optional。required 缺失必须阻断;optional 缺失则进入降级矩阵。这样版本升级和功能开关不再互相冒充。
这段代码解决必需能力、可选能力和降级动作的确定性计算。
interface CapabilityPolicy {
required: string[];
optional: string[];
}
interface CapabilityDecision {
requiredMatched: number;
requiredTotal: number;
missingRequired: string[];
missingOptional: string[];
decision: 'BLOCKED' | 'FULL_READY' | 'DEGRADED_READY';
disabledFeatures: string[];
}
function negotiateCapabilities(pluginCaps: string[],
policy: CapabilityPolicy): CapabilityDecision {
const actual = new Set(pluginCaps);
const missingRequired = policy.required.filter((x: string) => !actual.has(x));
const missingOptional = policy.optional.filter((x: string) => !actual.has(x));
if (missingRequired.length > 0) {
return {
requiredMatched: policy.required.length - missingRequired.length,
requiredTotal: policy.required.length,
missingRequired, missingOptional,
decision: 'BLOCKED', disabledFeatures: ['plugin.entry']
};
}
return {
requiredMatched: policy.required.length,
requiredTotal: policy.required.length,
missingRequired, missingOptional,
decision: missingOptional.length === 0 ? 'FULL_READY' : 'DEGRADED_READY',
disabledFeatures: missingOptional.map((cap: string) =>
cap === 'search.batch' ? 'batch.action' : `feature:${cap}`)
};
}
本批样例策略是 required=['search.preview']、optional=['search.batch']。插件提供预览能力,所以 required 为 1/1;缺少批处理能力,因此只关闭 batch.action,并保留预览入口。结果不是“兼容/不兼容”二选一,而是一条可解释降级路径。
容易出错的是把 optional 缺失记成 warning,却仍让组件自己决定是否展示。诊断报告和运行 UI 应消费同一份 disabledFeatures,否则构建日志说已降级,页面仍可能露出不可用按钮。
四、从 ohpm 安装事实提取审计输入
ohpm 是 OpenHarmony/HarmonyOS 生态中的三方包管理工具。项目清单和锁定结果告诉我们“请求了什么、解析成什么”,但业务插件的能力仍应来自受版本控制的元数据或插件自身清单。
工具链可以把三份输入合并:
oh-package.json5:项目声明的依赖范围。- 锁定或依赖清单:实际安装版本。
plugin-capabilities.json:插件能力与宿主策略。
本文不假设 ohpm 内部自动理解 capabilities。它只是提供已安装包的事实;PluginRangeLab 在构建前把事实转成一份稳定 JSON 报告,再由 ArkUI 诊断页读取。
这段代码解决多份清单合并、状态推进和报告留证。
type AuditState =
| 'DISCOVERED'
| 'PARSED'
| 'RANGE_REJECTED'
| 'CANARY_OPT_IN'
| 'CAPS_CHECKED'
| 'DEGRADED_READY';
interface AuditReport {
taskId: string;
checkedAt: string;
packageName: string;
installedVersion: string;
stableRange: string;
canaryRange: string;
states: AuditState[];
stableAccepted: boolean;
canaryAccepted: boolean;
capability: CapabilityDecision;
}
function buildReport(version: string, caps: string[]): AuditReport {
const stable = checkVersion(version, 'stable');
const canary = checkVersion(version, 'canary');
const capability = negotiateCapabilities(caps, {
required: ['search.preview'], optional: ['search.batch']
});
return {
taskId: 'SEMVER-CAP-0212', checkedAt: '21:36',
packageName: 'vision-bridge', installedVersion: version,
stableRange: stable.range, canaryRange: canary.range,
states: ['DISCOVERED', 'PARSED', 'RANGE_REJECTED',
'CANARY_OPT_IN', 'CAPS_CHECKED', 'DEGRADED_READY'],
stableAccepted: stable.satisfies,
canaryAccepted: canary.satisfies,
capability
};
}
状态序列保留了稳定通道被拒绝的事实。切换到 canary 不是“把失败改成成功”,而是一次显式策略变更,因此报告里同时保留 stableAccepted=false 和 canaryAccepted=true。这对发布复盘很关键:后来看到 DEGRADED_READY,仍能知道它不是稳定通道的常规启用。
开发配图同样是演示画面,不冒充实际 IDE 运行证据。画面中央是 checkVersion 与能力协商,右侧模拟器显示 PluginRangeLab,底部 HiLog 精确列出:stable=false、canary=true、required=1/1、optionalMissing=1、decision=DEGRADED_READY。

五、诊断页只展示报告,不重新判断
如果 ArkUI 页面重新实现一遍版本范围,很容易与构建脚本漂移。更稳妥的做法是:构建工具生成 plugin-audit.json,应用只读取并展示。发布构建可以在 BLOCKED 时直接失败;内部诊断包则保留页面,方便定位来源。
这段代码解决诊断页如何消费既有报告,而不是复制门禁逻辑。
@Entry
@Component
struct CapabilityAuditPage {
@State report: AuditReport = buildReport('3.0.0-beta.4', [
'search.preview', 'search.basic', 'telemetry.trace'
]);
build() {
Scroll() {
Column({ space: 12 }) {
Text('插件能力审计').fontSize(28).fontWeight(FontWeight.Bold)
Text(this.report.taskId)
Text(`${this.report.packageName}@${this.report.installedVersion}`)
Row() {
Text(`稳定通道 ${this.report.stableAccepted ? 'PASS' : 'REJECTED'}`)
Blank()
Text(`Canary ${this.report.canaryAccepted ? 'ACCEPTED' : 'REJECTED'}`)
}.width('100%')
Text(`必需能力 ${this.report.capability.requiredMatched}/${this.report.capability.requiredTotal}`)
Text(`缺失可选能力 ${this.report.capability.missingOptional.length}`)
Text(this.report.capability.decision).fontColor('#B3261E')
ForEach(this.report.capability.disabledFeatures,
(feature: string) => Text(`已关闭:${feature}`))
}.padding(24).width('100%')
}
}
}
这里为了文章可读性直接调用 buildReport() 生成固定样例;真实发布工程应读取由门禁脚本落盘的报告,并验证报告摘要、策略版本和生成时间。页面不得把 REJECTED 改成 ACCEPTED,也不得用按钮绕过 required 缺失。
运行图显示 21:36、5G、79% 电量,任务 SEMVER-CAP-0212,插件 vision-bridge@3.0.0-beta.4。稳定通道被拒绝,canary 明确接受,最终进入 DEGRADED_READY。红色箭头只标出预发布范围和缺失的 search.batch,避免把整张页面做成批注海报。

六、四种版本输入,四种处理结果
一个门禁能否长期工作,取决于边界样本而不是顺利样本。本文至少保留四组黄金向量。
第一组是稳定版本 2.9.3。它满足 >=2.8.0 <3.0.0,若必需能力齐全,可以进入 stable 的 FULL_READY 或 DEGRADED_READY。
第二组是本文主样本 3.0.0-beta.4。稳定范围拒绝;canary 显式范围接受;再进入能力门。
第三组是 3.0.1-beta.1。即使它在排序上比 3.0.0-beta.2 新,默认也不能因为 canary 范围包含 3.0.0-beta.2 就顺便放行另一个 patch 元组。官方 prerelease 规则正是为了阻止这种无意扩大。
第四组是 release-3-beta。严格 valid() 返回 null。门禁应输出 INVALID_CONTRACT,保留原字符串,并要求上游修清单;不能 coerce 成一个貌似合理的稳定版本。
此外,还要测试构建元数据。3.0.0-beta.4+sha.91ad 的 precedence 与不带 build metadata 的对应版本相同,但报告仍可保留完整原值做溯源。不要把 build metadata 当成能力或风险级别。
诊断详情图把四个向量、两条范围和能力矩阵放在同一条审计链上:
- stable:
>=2.8.0 <3.0.0,主样本 REJECTED。 - canary:
>=3.0.0-beta.2 <3.0.0,主样本 ACCEPTED。 - required:
search.preview,1/1。 - optional:
search.batch,missing 1。 - disabled:
batch.action。 - decision:
DEGRADED_READY。

七、发布门禁需要保留的五份证据
只把最终决定打印成一行日志不够。建议报告至少包含以下证据。
一是原始版本和规范化版本。两者不同就要说明原因;严格模式下通常不应默默改变。
二是通道与范围。stable、canary 不是环境变量里的随意字符串,而是审批策略的一部分。谁改了 canary 范围,应能从代码评审和报告摘要里追到。
三是 prerelease 数组。['beta', 4] 比手写字符串截取更可靠,也能区分稳定版本。
四是能力差集。报告不要只写 missing=1,要写出 search.batch,并映射到 batch.action。运维看到按钮隐藏时,才能把 UI 变化和插件事实对应起来。
五是工具版本与策略版本。node-semver 的行为由实现版本和 SemVer 规范共同决定;项目策略也会演进。黄金向量应在升级三方库时重新运行,避免工具更新改变边界结果而无人察觉。
在生命周期上,构建脚本是一次性进程,不涉及页面回调释放;诊断页若订阅文件变化或内部事件,则仍要在退场时 off。不要因为它是“内部工具页”就允许监听器常驻,热更新或反复进入会让同一报告被重复加载。
八、不要让 semver 替业务做它做不到的事
SemVer 范围可以表达版本接受区间,却不能证明 API 行为、数据格式、权限、性能和安全边界。能力清单能表达插件声明,却也不能替代集成测试。本文把两者串成门禁,是为了更早阻断明显不一致,而不是给“兼容性”盖最终章。
以下情况要直接扩大验证范围:插件跨 major;能力虽存在但参数合同变化;beta 版本包含本地 native 库;多个 HAP/HSP 引用了不同版本;宿主需要回滚;线上数据格式不可逆迁移。此时除了范围和能力,还要检查包体、ABI、迁移脚本与回滚路径。
本文也没有宣称 ohpm 会采用 node-semver 的全部细节。项目的依赖解析规则应以当前 ohpm 文档与实际锁定结果为准。node-semver 在这里是项目自建审计工具,负责解释插件业务合同;它不取代包管理器。
最后留下一个简单原则:稳定通道不要猜测团队愿意承担的预发布风险,canary 通道不要从版本号猜测功能。范围写出意图,能力写出事实,降级矩阵写出用户最终看到什么。
九、门禁本身也要有升级纪律
版本门禁一旦进入发布链,就不能被当成永远正确的黑盒。三方 semver 包会升级,团队策略会调整,ohpm 的依赖事实也可能因为锁文件或仓库变化而改变。真正可维护的做法,是把门禁看成一项有输入、有版本、有黄金向量的产品能力。
首先固定工具版本。审计脚本使用的 semver 版本应进入锁定文件,并在报告里记录。若只写 ^ 范围又不保存实际解析结果,同一份插件清单可能在不同构建节点上得到不同实现版本。本文不假设不同版本一定产生不同结论,但发布证据必须能回答“当时由哪一版工具判断”。
其次给策略单独编号,例如 plugin-policy-v4。稳定范围、canary 范围、required、optional 和能力到 UI 的映射都属于策略。只改一条范围也要提升策略版本,因为它改变了可发布集合。不要把这些值散在页面、脚本和配置中心三个位置。
再次保存原始输入摘要。报告应对项目依赖清单、实际安装清单、插件能力清单与策略文件分别计算摘要。这样回看 DEGRADED_READY 时,能够确认它对应哪四份输入,而不是只剩一张不可重现的截图。
1. 黄金向量要覆盖“看起来应该通过”的失败样本
很多测试只写明显正确和明显错误两端,恰好遗漏 prerelease 最容易误判的中间地带。除了文中的四个版本,还应该补充这些向量。
3.0.0-beta.1 低于 canary 下界 beta.2,必须拒绝。3.0.0-beta.10 高于 beta.4,但是否允许取决于范围,而不是字符串字典序。3.0.0 是稳定版本,却不满足 <3.0.0;这提醒团队 canary 合同在正式版发布时要显式迁移,而不能期待范围自动延续。
v3.0.0-beta.4 是否接受,要由输入规范决定。node-semver 为兼容历史会处理前导 v,但项目可以选择更严格的清单规则并拒绝它。关键是规则写进策略,不要一处接受、一处报错。
带 build metadata 的 3.0.0-beta.4+sha.91ad 在优先级比较中不因 metadata 提高或降低,但报告应保留完整原值。若发布平台把不同构建摘要视为不同制品,还需要在 semver 决定之后增加制品身份校验,不能把两者混为一谈。
2. 能力清单也可能撒谎
插件声明 search.preview,只说明它声称具备能力。宿主仍应有最小探针,例如加载固定输入并检查返回结构、超时和错误类型。探针失败时,最终决定应从 FULL_READY 或 DEGRADED_READY 降到 BLOCKED_RUNTIME_PROBE,而不是继续相信静态清单。
探针不要承载完整性能测试。它只回答接口是否能按最小合同工作;耗时、内存、精度与异常恢复应由独立集成测试负责。门禁层次越清楚,失败时越容易知道该找包维护者、宿主适配层还是发布流水线。
能力名称也要版本化。search.preview 的参数或结果语义发生不兼容变化时,不应继续沿用同一字符串。可以升级为 search.preview.v2,或在能力描述里增加独立 schemaVersion。仅把插件 major 升到 4,仍无法告诉宿主某项能力的精确数据合同。
十、回滚不是把版本号改小
当 canary 试用失败,最直觉的操作是把插件版本退回 2.9.3。但如果 beta 插件已经写入新格式缓存、索引或配置,降级安装并不等于数据可回滚。版本门禁必须与数据迁移证据相邻,而不能只盯着依赖行。
本文 Demo 没有不可逆数据,因此回滚矩阵很简单:关闭 canary 策略,恢复稳定版本,重新生成审计报告,确认 required 与 optional,再重新跑最小探针。真实插件若有数据写入,应额外声明 dataSchema、minReadableSchema 和迁移方向。
如果 beta 写入 schema 4,而稳定版最多读 schema 3,那么“退回 2.9.3”应该被门禁拒绝,除非存在已验证的降级迁移。此时最安全的操作可能是保持 beta 二进制但关闭功能,先导出或转换数据。依赖版本只是回滚计划的一部分。
多模块工程还要检查是否存在双版本。一个 HAP 引用稳定版,另一个 HSP 或测试模块引用 beta,可能让两套能力清单同时进入制品。ohpm 的递归依赖视图能帮助定位安装事实,业务审计则要按最终模块边界确认实际加载者。不能看到根清单只有一条依赖,就假设制品里只有一个实现。
发布流水线建议把决定分成四级。PASS 表示稳定通道、能力齐全、探针通过;DEGRADED 表示显式通道允许、必需能力齐全、可选能力有缺失且 UI 已同步关闭;BLOCKED 表示范围、必需能力或探针失败;INVALID 表示输入本身无法解释。四级比单一退出码更适合诊断,但真正发布时,只有策略明确允许的级别才能返回成功。
十一、一次版本审计应该怎样被读懂
拿到 SEMVER-CAP-0212 报告时,审查者不需要先阅读脚本。第一页就应回答:检查了哪个包、实际安装版本是什么、处于哪个通道、两条范围分别怎样、预发布标记是什么、required/optional 差集是什么、最终禁用了哪个入口。
第二页再展开证据:原始清单位置、锁定结果、工具版本、策略版本、摘要和黄金向量。这样日常排查只看结论,发布复盘仍能追到底层输入。把所有内容塞进一长串 HiLog,既不利于人读,也不利于机器比较。
日志字段最好保持稳定键名。stable=false、canary=true、required=1/1、optionalMissing=1、decision=DEGRADED_READY 已经足够表达主路径。不要今天写 status、明天写 result、后天改成自然语言,否则跨版本报告很难做差异比对。
审计页面也应标明它是生成报告的呈现,不是运行时事实源。若页面加载失败,发布门禁仍由构建报告决定;若构建报告阻断,页面上的绿色样式不能覆盖它。这种单向关系和本文第一篇的状态协调器类似:展示者消费事实,但不能自封为事实生产者。
最后,门禁失败不应该诱导开发者直接放宽范围。先判断失败属于哪一层:版本没有被通道选择、必需能力缺失、可选能力降级、清单无效、运行探针失败,还是数据不可回滚。只有明确原因后,修改范围才有意义。
这套流程的价值不在于多了一张诊断页,而在于把“装上了所以应该能用”改成一条可审计的推理链。版本规则、通道意图、能力事实、界面降级和回滚条件各自有位置,任何一层变化都能留下清晰证据。
十二、参考与版本边界
- node-semver 官方仓库与 prerelease 规则:https://github.com/npm/node-semver
- npm Semantic Versioning 说明:https://docs.npmjs.com/about-semantic-versioning/
- HarmonyOS ohpm 常见问题与工具说明:https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-command-line-tool-34
- HarmonyOS 命令行工具文档中心:https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-command-line-tool
文中 stable/canary、capabilities、disabledFeatures 与审计状态机均为应用工程策略。实际项目应固定 semver 工具版本,使用严格输入,保存黄金向量,并以当前 ohpm 锁定结果验证安装事实。
更多推荐





所有评论(0)