# conti-retail-app 大陆马门店 App。Flutter + Melos 单仓多包。 架构约束全部来自相邻仓库 `conti-docs/` 的 01–14 号文档。**本仓库对这些文档的 偏离,逐条记在 [SCAFFOLD-NOTES.md](SCAFFOLD-NOTES.md)**——那份文件是回写文档的 依据,改动之前先看一眼。 --- ## 怎么跑起来 三步。不要跳过第二步。 ```bash # 0. 工具链(一次性) fvm use 3.44.9 # 或者保证 PATH 上的 flutter 就是 3.44.9 dart pub global activate melos 8.2.2 # 1. 装依赖 melos bootstrap # 2. 生成代码(riverpod 的 *.g.dart 不入库,不跑这步全仓库编译不过) melos run gen # 3. 跑起来 cd app flutter run --flavor dev -t lib/main_dev.dart --dart-define-from-file=env/dev.json ``` `--flavor` / `-t` / `--dart-define-from-file` **三个参数缺一不可**: - 少了 `--dart-define-from-file`,`AppEnv.fromDartDefine` 会在启动瞬间抛错。 这是故意的——带着空 baseUrl 跑起来,问题会在第一个请求 404 时才暴露。 - 少了 `-t`,Flutter 会去找 `lib/main.dart`,本仓库没有这个文件。 VS Code / Android Studio 用户建议把三条 flavor 配进 `launch.json`。 ## 环境与工具链版本 跑通时的实测版本,与 `pubspec.lock` 一致: | | 版本 | 备注 | |---|---|---| | Flutter | 3.44.9 | `.fvmrc` 里锁着 | | Dart | 3.12.2 | **Flutter 自带的那个**,见下面的坑 | | Melos | 8.2.2 | 配置内联在根 `pubspec.yaml`,没有 `melos.yaml` | | flutter_riverpod | 3.3.2 | | | riverpod_annotation | 4.0.3 | | | riverpod_generator | 4.0.4 | | | build_runner | 2.15.1 | | | analyzer | 12.1.0 | | > **riverpod 的版本被 `flutter_test` 卡着。** `flutter_test` pin 了 > `test_api 0.7.11`,往上升 riverpod 会拉起不兼容的 `analyzer`,解析直接失败。 > 想升级先确认这条链路,不要只看 pub.dev 上的最新版。 ### 坑:机器上有两个 Dart SDK 如果你的 PATH 上装了独立的 Dart SDK(`dart --version` 不等于 3.12.2),那么 ```bash dart run build_runner build # ← 会用错 SDK,报一堆看不懂的解析错误 ``` 要显式用 Flutter 自带的那个: ```bash export FDART="$(dirname "$(which flutter)")/cache/dart-sdk/bin/dart" $FDART run build_runner build # fvm 用户:~/fvm/versions/stable/bin/cache/dart-sdk/bin/dart ``` `melos run gen` 内部走的是 `dart run`,所以同样受影响。最省事的做法是让 PATH 上只有 Flutter 自带的 Dart。 ### 坑:melos 命令 pub cache 里只有 `melos.bat`,git-bash 下直接敲 `melos` 可能找不到。用: ```bash dart pub global run melos:melos ``` ## 常用命令 ```bash melos run gen # 代码生成(riverpod) melos run gen:watch # 开发期常驻 melos run gen:pigeon # native_* 的 Pigeon 产物(产物入库) melos run analyze # flutter analyze --fatal-infos,含 riverpod_lint melos run format # dart format --set-exit-if-changed melos run test # flutter test --coverage ``` `analyze` 带 `--fatal-infos`。不加等于没加 lint——绝大多数 riverpod_lint 规则 报的是 info 级。 `riverpod_lint` 由 `flutter analyze` 直接执行(顶层 `plugins:` 映射),**不再 需要 `custom_lint`**,也没有 `dart run custom_lint` 这一步。文档 03/14 写的还是 旧方案,见 SCAFFOLD-NOTES §B。 ## 包结构与依赖规则 ``` app/ 壳工程。唯一知道所有包的地方:环境注入、启动编排、路由聚合 packages/ core_foundation/ AppEnv / AppException 体系 / ApiCode ← 叶子包,谁都能依赖 core_logging/ AppLogger / CrashReporter / 脱敏 core_analytics/ Analytics 接口 + 事件常量表 core_storage/ Prefs(KV)。Drift 暂缓,见 SCAFFOLD-NOTES core_auth/ AppSession 四态 / TokenStorage / SessionNotifier core_network/ dio + 4 个拦截器 + ApiClient core_router/ goRouterProvider / menuRouteMap / go_router re-export core_ui/ 主题 / AsyncValueView / ErrorPresenter core_webview/ UrlGuard / JSBridge / WebViewSession feature_auth/ 登录、选店 feature_home/ 工作台 native_scan/ Pigeon 扫码接口(原生实现待补) ``` 依赖规则(01): - `feature_* → core_* / native_*`。**feature 之间禁止互相依赖**——两个 feature 要共享东西,说明那个东西属于某个 core_*。 - `core_* → core_*` 只允许三条边:`core_network → core_auth`、 `core_router → core_auth`、`core_webview → core_auth`。外加所有包都可以依赖 叶子包 `core_foundation`。 - `native_* ` 只依赖 Flutter SDK 和 Pigeon 产物,**一个 core_* 都不依赖**。 - feature 的 pubspec 里**不出现 `go_router`**:路由类型由 `core_router` re-export。 ### 这些规则靠 `--fatal-infos` 守,不是靠编译器(实测结论) 直觉上会以为「没在 pubspec 里声明就 import 不到」。**在 Pub Workspace 里这是错的**: 所有成员包共用根目录一份 `.dart_tool/package_config.json`,任何成员都能解析到 任何其他成员。实测在 `feature_home` 里 import `feature_auth` 而不声明依赖: ``` flutter test → All tests passed! ← 编译通过,跑得起来 flutter analyze → info: depend_on_referenced_packages ``` 只有一条 **info**。所以: > **`melos run analyze` 的 `--fatal-infos` 是这套包边界唯一的强制点。** > 谁把它从 CI 里拿掉,或者在某个包里 ignore 掉 > `depend_on_referenced_packages`,边界当天就失效,而且没有任何别的信号。 想自己复现:在 `feature_home/lib/src/` 下扔一个 import `feature_auth` 的文件, `flutter test` 是绿的,`flutter analyze --fatal-infos` 是红的。 ### 那些 `throw UnimplementedError('必须在 bootstrap 里 override')` 依赖规则会挡住一些**合理**的调用(比如 core_auth 想发 HTTP、core_webview 想 换票)。这些地方一律用依赖反转解决:包内声明 `abstract interface` + 一个会抛错 的 provider,实现落在 `app/lib/bootstrap.dart` 里 override 进去。 新增一个这样的端口时,**必须同时在 bootstrap 里接上**——它是运行期才炸的, 编译器帮不了你。 ## 待办 / 阻塞项 - **iOS flavor 未配置**:Scheme 和 Build Configuration 只能在 Xcode 里建, 步骤见 [`app/ios/FLAVORS.md`](app/ios/FLAVORS.md)。目前没有 Mac 构建机。 - **native_scan 没有原生实现**:Pigeon 接口和 Dart 侧齐了,Kotlin/Swift 侧是 模板。现在调 `startScan` 会抛 `MissingPluginException`,属预期。 - **神策 SDK 未采购**:`analyticsProvider` 是 `NoopAnalytics`。接入时注意必须在 用户同意隐私政策之后再初始化。 - **env/*.json 里全是占位域名**,`SENTRY_DSN` 全空,待运维确认。 - **CI 镜像名未定**,`.gitlab-ci.yml` 里标了 TODO(ops)。