diff --git a/scripts/new-seam.sh b/scripts/new-seam.sh new file mode 100755 index 0000000..9feb86f --- /dev/null +++ b/scripts/new-seam.sh @@ -0,0 +1,77 @@ +#!/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。"