#!/usr/bin/env bash # new-seam.sh — 闸1 脚手架:一条命令生成一个「接缝」骨架(支柱1:接缝即接口)。 # # 让「对的写法」成为阻力最小的默认路径:外部依赖(原生/HTTP/子进程/存储/时钟/随机) # 藏在接口后、可注入、自带 Fake,从一开始就可测(支柱1 + 支柱5)。 # # 用法: bash scripts/new-seam.sh [lib 子目录(默认 seams)] # 例: bash scripts/new-seam.sh GeoLocator services # → client/lib/services/geo_locator.dart (接口+Impl+Fake+provider) # → client/test/unit/geo_locator_seam_test.dart (ProviderScope override 桩) set -euo pipefail NAME="${1:-}" SUBDIR="${2:-seams}" [ -n "$NAME" ] || { echo "用法: bash scripts/new-seam.sh [lib子目录]" >&2; exit 2; } case "$NAME" in [A-Z]*) ;; *) echo "✗ 名字须 PascalCase(首字母大写),如 GeoLocator" >&2; exit 2;; esac ROOT="$(cd "$(dirname "$0")/.." && pwd)" # PascalCase → snake_case SNAKE="$(python3 -c 'import re,sys;print(re.sub(r"(?/dev/null || python3 -c 'import sys;s=sys.argv[1];print(s[0].lower()+s[1:])' "$NAME")" LIB="$ROOT/client/lib/$SUBDIR/$SNAKE.dart" TEST="$ROOT/client/test/unit/${SNAKE}_seam_test.dart" [ -e "$LIB" ] && { echo "✗ 已存在: $LIB" >&2; exit 1; } [ -e "$TEST" ] && { echo "✗ 已存在: $TEST" >&2; exit 1; } mkdir -p "$(dirname "$LIB")" cat > "$LIB" < doThing(); } /// 真实现:连真实外部依赖。 class ${NAME}Impl implements $NAME { // TODO: 实现接口方法。 } /// 测试 Fake:无副作用、可控,供 ProviderScope override 注入(支柱1)。 class ${NAME}Fake implements $NAME { // TODO: 返回可控的假数据/记录调用。 } /// 注入点:默认真实现;测试 override 为 ${NAME}Fake。 final ${LOWER}Provider = Provider<$NAME>((ref) => ${NAME}Impl()); EOF cat > "$TEST" <()); // TODO: 驱动接口方法、断言 Fake 行为(支柱5:可观测即可断言)。 }); } EOF echo "✓ 已生成接缝骨架:" echo " lib : client/lib/$SUBDIR/$SNAKE.dart (接口 + Impl + Fake + provider)" echo " test: client/test/unit/${SNAKE}_seam_test.dart (override 注入 Fake)" echo "下一步:补接口方法 + Impl/Fake + 用例;flutter analyze && flutter test。"