关于 dependency-hygiene
dependency-hygiene 是面向 Flutter/Dart 应用开发的依赖管理技能,涵盖锁文件、依赖审计、升级与本地托管流程,并检查传递依赖。
主要功能
- 01依赖范围与锁文件规则
- 02传递依赖审计
- 03SDK 与 lint include 检查
- 04升级及真机验证指南
- 05通过接口托管插件的指南
适用场景
- 01审查 pubspec.yaml 变更
- 02引入新包前进行审计
- 03升级 SDK 或原生插件
- 04移除或本地托管依赖
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ zakariaf/ flutter-skills --skill dependency-hygiene安装后在 Claude Code 中输入/dependency-hygiene确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
examples/vendored_plugin_behind_interface.dartreferences/dependency-gate-and-audit.mdsdk-pin-and-lint-include.mdvendoring-behind-an-interface.mdscripts/audit_deps.pyaudit-deps.sh详细介绍
依赖卫生
每个依赖都是永久的负担,迟早可能需要由其他人维护。应优先考虑 多年后仍能解析并构建,而不是 追最新版本。本规范涵盖 pubspec/lock 机制、新软件包必须通过的门槛,以及关键软件包腐朽时的退路。
根据当前任务阅读参考资料:
references/dependency-gate-and-audit.md— 拒绝/接受门槛、传递依赖审计、许可证记录和升级纪律。references/sdk-pin-and-lint-include.md— 记录 SDK 版本,以及每次升级 SDK 时可能悄无声息禁用 lint 的陷阱。references/vendoring-behind-an-interface.md— 将 bus-factor 为 1 的原生插件引入third_party/,同时不改动lib/。
提交修改 pubspec.yaml 的 PR 前,运行 scripts/audit-deps.sh。
不可妥协的规则
pubspec.yaml中使用 Caret 范围,pubspec.lock中才使用精确锁定版本。drift: ^2.31.0,绝不要写成drift: 2.31.0。锁文件负责锁定版本;范围只用于确保依赖仍可解析。在 pubspec 中精确锁定版本会在下次升级 SDK 时造成无法解决的冲突,而锁文件已经提供了这种精确锁定,因此毫无益处。- 提交
pubspec.lock。 这是应用而非软件包——正是这种区别让规则有所不同。默认的 Dart.gitignore模板会列出pubspec.lock;请删除这一行。提交锁文件,才能确保其他人git clone后解析到曾在真实设备上测试过的确切版本。 - 只修改
pubspec.yaml、不更新pubspec.lock,这份改动就不完整。 运行flutter pub get,并在同一个提交中暂存锁文件;添加、升级和移除依赖都一样。 - 单独记录 SDK 版本,不要与工具版本混淆。 保持
environment: sdk:为真实的版本范围,以便pub能解析;将经过测试的确切 Flutter 版本记录在 CI 会读取的已提交文件中(参见参考资料)。其他人和 CI 读取的是该记录;不要把版本管理工具和文件混为一谈。 - lint 的 include 文件名与解析出的 SDK 绑定。 如果版本锁定的
include:指向解析出的 linter 软件包中不存在的文件,就会产生include_file_not_found——这会导致默认的dart analyze/flutter analyze失败,因此标准 CI 会捕获此问题。只有在警告不作为致命错误处理时,它才会悄无声息地通过;即便如此,停止生效的也是规则集新增或提升的规则,而不是 analyzer 内置规则。每次升级 SDK 后都要验证(参见参考资料)。 - 添加依赖前审计传递依赖树,而不只是检查 pubspec。 被禁用的 SDK 恰恰可能从第二层依赖引入。在提交新依赖前,运行
scripts/audit-deps.sh(或运行dart pub deps --json并结合审计脚本)。 - 根据政策拒绝任何会建立网络连接、报告崩溃/用量、引入遥测核心,或为尚未发布的功能收集设备标识符的依赖——无论是直接还是传递引入。CI 通过不代表原生功能有效。参见门槛参考资料。
- 移除依赖后,在
lib/中执行 grep。 从pubspec.yaml删除的软件包仍可能通过传递依赖被解析,因此今天还能编译,却会在真正重要的克隆环境中出错。analyzer 能发现无法解析的导入,却发现不了意外解析成功的导入。 - bus-factor 为 1 的原生插件现在就通过接口封装,之后再考虑引入本地副本。 接口是成本低廉的保险;在故障尚未发生时就提前引入本地副本,只会增加维护负担。参见本地引入参考资料。
pubspec 使用范围,锁文件锁定版本
| 应该做 | 绝不要做 |
|---|---|
Caret 范围:drift: ^2.31.0 | 精确锁定:drift: 2.31.0 |
environment: sdk: ^3.6.0 — 真实的版本范围 | 精确的 sdk: 版本 |
提交 pubspec.lock | 将 pubspec.lock 加入 gitignore |
锁文件具有权威性。范围让求解器能在升级 SDK 时调整版本;锁文件则确保所有人使用实际验证过的版本。
每个新依赖都必须通过的关卡
如果某个软件包本身或其传递依赖树中的任何项目存在以下任一情况,就拒绝它:
- 在产品承诺无网络或离线优先时,打开你未选择的网络通路(
http、dio、套接字、gRPC)。 - 违反无遥测政策,报告崩溃或使用情况(Crashlytics、Sentry、任何分析或归因 SDK)。
- 通过传递依赖引入遥测/分析核心——即使你从未调用崩溃 SDK 的核心,也可能因此影响应用商店的隐私标签。
- 为你未发布的功能收集设备标识符。
- 需要
--enable-experiment标志——一个被弃用、需要实验功能才能构建的仓库,会导致构建无法继续。
还要考虑以下因素,并通常应予以拒绝:任何未通过接口隔离的单人维护软件包;任何功能只需几百行第一方 Dart 代码即可实现的软件包;任何仅仅为了少敲几行代码而存在的软件包。记录许可证——宽松许可证(MIT、BSD、Apache-2.0)是使用 vendoring 规避方案的前提;Copyleft 依赖会让你无法选择该方案。完整清单见 references/dependency-gate-and-audit.md。
审计传递依赖树
dart pub add --dry-run <package> # see what would resolve, resolve nothing
dart pub deps --json > /tmp/deps.json
scripts/audit-deps.sh # walks the resolved set, flags banned patterns
审计会遍历完整的已解析依赖图,将每个名称与策略模式列表进行匹配,为每个命中项标记 direct 或 TRANSITIVE,并区分会随 APK 发布的依赖与仅用于构建/测试的依赖(如果被禁软件包仅能通过 dev_dependencies 引入,就不会进入二进制文件,因此不构成发布缺陷)。退出码为 1 表示应拒绝该依赖,或找到不会引入这些软件包的替代项。要查找是谁引入了某个软件包:dart pub deps | grep -B4 <name>。
修改 scripts/audit_deps.py 顶部的 BANNED/ALLOW 列表以符合你的政策;每个 ALLOW 条目旁都必须附上书面理由。
升级
按自己的计划运行 dart pub outdated;没有机器人负责这项工作。Dependabot 从未支持 pub;Renovate 虽会创建 PR,但单人维护的仓库没有处理 PR 的工作流。 手动保留它们唯一有用的做法:绝不要盲目升级应用核心能力所依赖的原生插件。
CI 通过并不能证明原生能力正常。 在无头模拟器上运行的任何 CI 作业,都无法证明应用能播放音频、读取传感器或端到端地与平台通道通信——integration_test 只能断言平台通道调用已发出,仅此而已。每次升级关键能力的原生插件或 SDK 后,都要在真实设备上手动测试。每次升级涉及代码生成软件包(drift、freezed、json_serializable)后,都要重新生成并提交代码——过期的生成代码会由 CI 检出,而不是由分析器检出(见 run-codegen)。
通过接口隔离单人维护的插件
对于单人维护的原生插件,如果它出故障会导致应用无法构建,那么这是唯一值得提前规划应对方案的依赖。缓解措施是在第一天就编写一个轻量接口,这样 vendoring 时就无需改动 lib/:
// A port the app depends on; the plugin lives behind it. The `Gateway` suffix
// marks a thin wrapper over a SPECIFIC plugin (here flutter_secure_storage) —
// `naming-conventions` owns that Service-vs-Gateway distinction.
abstract interface class SecureStorageGateway {
Future<void> write(String key, String value);
Future<String?> read(String key);
}
只有在插件确实出问题时——例如无法在某个 Flutter 版本下构建,或上游发布了不会修复的回归——才将其克隆到最后一个可用标签对应的 third_party/<plugin>/,在 pubspec 中将其指向 path:,在 VENDORED.md 中记录 SHA 和每一处改动,确认许可证允许再分发,并且只打补丁,不做重构。完整流程与触发条件见 references/vendoring-behind-an-interface.md。另见 examples/vendored_plugin_behind_interface.dart。
移除依赖
删除 pubspec 条目,运行 flutter pub get,并在同一个提交中提交 pubspec.lock 的差异——然后运行 grep -r "package:<name>" lib/。今天能通过传递依赖解析的导入,也会让重要的克隆构建失败。
多包工作区
在 Dart pub 工作区/单体仓库中,每个包都在自己的 pubspec.yaml 中保留版本范围,但工作区会在根目录解析出一份共享锁文件——请审计并提交这份根目录锁文件。审计时应针对整个工作区解析后的依赖树,而不是单个包。单个包的 pubspec 无法显示某个兄弟包为共享解析带入了什么依赖。工作区工具链机制请参阅 codegen-and-toolchain。
反模式
- 在
pubspec.yaml中精确固定版本。 这会让下次 SDK 升级变成一场无法解决冲突的调试会话;锁文件已经固定了版本。 - 在应用中将
pubspec.lock加入 Git 忽略。 全新克隆时,解析出的版本取决于pub当下的选择,而不是经过测试的版本。 - 修改 pubspec 却不提交锁文件变更。 下一台机器会解析出不同的依赖关系图。
- 通过读取
pubspec.yaml判断依赖是否安全。 被禁止的 SDK 位于第二层依赖中,在那里看不到。 - 相信原生功能的 CI 绿灯。 模拟器没有语音引擎/真实传感器;绿灯只能证明 Dart 编译通过,不能证明功能有效。
- 提前 vendoring 一个运行正常的插件。 你会在问题出现前就承担维护负担;接口才是成本低廉的保险,而不是 fork。
- 通过重构进行 vendoring。 你在
third_party/中改动的每一行,往后都由你负责维护——修补问题后就停手。 - 添加没有接口、且 bus factor 为 1 的包。 等它腐朽时,你将没有可以 vendoring 到其后的接缝。
完成定义
- 新增/变更的依赖使用插入符版本范围;任何
pubspec.yaml中都没有精确固定版本。 -
pubspec.lock的变更与pubspec.yaml变更在同一提交中暂存。 -
scripts/audit-deps.sh退出码为 0(或每条命中项都有合理的ALLOW条目)。 - SDK 升级时:更新版本记录,并确认解析后的包中存在固定版本的 lint
include:文件(references/sdk-pin-and-lint-include.md)。 - 记录任何新依赖的许可证;若其可能用于 vendoring,则许可证必须宽松。
- 关键能力所依赖的原生插件位于接口之后;升级后已在真实设备上进行测试。
- 移除依赖后:
grep -r "package:<name>" lib/无匹配结果。
相关技能
codegen-and-toolchain— 工作区链接、工具链层面的 SDK 固定,以及生成代码的提交与 Git 忽略策略。run-codegen— 升级代码生成包后要运行的确定性build_runner流程。lint-and-style-config— 版本固定的include:所引用的严格analysis_options.yaml。service-boundary-and-native— vendoring 接缝所依赖的按副作用划分、可注入的接口模式。naming-conventions— 负责Service与Gateway的边界后缀规则;SecureStorageGateway这类插件封装使用Gateway后缀。ci-pipeline-and-gates— 将审计和锁文件新鲜度检查接入 CI。
参考资料
- Dart — 包依赖与版本约束:https://dart.dev/tools/pub/dependencies
- Dart —
pubspec.lock与术语表:https://dart.dev/tools/pub/glossary#lockfile - Dart —
dart pub deps:https://dart.dev/tools/pub/cmd/pub-deps - Dart —
dart pub outdated:https://dart.dev/tools/pub/cmd/pub-outdated - Dart — Pub 工作区:https://dart.dev/tools/pub/workspaces
- Flutter — 添加插件包/路径依赖:https://docs.flutter.dev/packages-and-plugins/using-packages