Initial project

This commit is contained in:
Cody
2026-03-06 14:56:17 +08:00
parent 977b627b15
commit bf9e099747
1180 changed files with 50973 additions and 0 deletions

308
scripts/new_sdk.sh Executable file
View File

@@ -0,0 +1,308 @@
#!/bin/bash
# 创建新的 SDK 包
#
# 流程:
# 1. flutter create --template=plugin 生成完整 Flutter 插件骨架(含原生代码)
# 2. mason make clean_plugin_sdk 覆盖写入 Facade+Wiring 架构
# 3. 修正 pubspec.yaml 注入 resolution: workspace + 统一版本约束
# 4. 更新 IDE 配置 .iml + modules.xml
# 5. dart pub get
#
# 用法:
# melos run new:sdk -- push # 创建 packages/push_sdk/
# melos run new:sdk -- push_sdk # 同上_sdk 后缀可省略
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
if [[ $# -eq 0 ]]; then
echo "Usage: melos run new:sdk -- <name>"
echo "Example: melos run new:sdk -- push"
exit 1
fi
INPUT="$1"
BASE_NAME="${INPUT%_sdk}"
PKG_NAME="${BASE_NAME}_sdk"
PASCAL_NAME=$(python3 -c "
parts = '${BASE_NAME}'.split('_')
print(''.join(p.capitalize() for p in parts))
")
PKG_DIR="$ROOT_DIR/packages/$PKG_NAME"
if [[ -d "$PKG_DIR" ]]; then
echo "Error: packages/$PKG_NAME already exists"
exit 1
fi
# mason 路径解析PATH 里找不到时 fallback 到 pub-cache 绝对路径
# setup.sh 把 mason 装到 ~/.pub-cache/bin需要重启终端 PATH 才生效
if command -v mason &> /dev/null; then
MASON_CMD="mason"
elif [[ -x "$HOME/.pub-cache/bin/mason" ]]; then
MASON_CMD="$HOME/.pub-cache/bin/mason"
else
echo "Error: mason not found."
echo "Run 'bash scripts/setup.sh' to install required tools."
exit 1
fi
# ---- 失败回滚 ----
# set -e 触发退出时自动还原:删目录、撤 workspace 条目、撤 modules.xml 条目
_cleanup_on_error() {
local code=$?
[[ $code -eq 0 ]] && return 0
trap - EXIT # 防止函数内部错误触发递归
echo ""
echo "Error (exit $code). Rolling back..."
[[ -d "$PKG_DIR" ]] && { rm -rf "$PKG_DIR"; echo " Removed packages/$PKG_NAME"; }
python3 - "$ROOT_DIR/pubspec.yaml" "$PKG_NAME" << 'PY_ROLLBACK'
import sys
path, pkg = sys.argv[1], sys.argv[2]
with open(path) as f: lines = f.readlines()
lines = [l for l in lines if l != f' - packages/{pkg}\n']
with open(path, 'w') as f: f.writelines(lines)
PY_ROLLBACK
python3 - "$ROOT_DIR/.idea/modules.xml" "$PKG_NAME" << 'PY_ROLLBACK'
import sys, re
path, pkg = sys.argv[1], sys.argv[2]
with open(path) as f: content = f.read()
content = re.sub(rf'\s*<module[^>]*packages/{re.escape(pkg)}/[^>]*/>\n?', '', content)
with open(path, 'w') as f: f.write(content)
PY_ROLLBACK
echo " Rollback complete. Re-run after fixing the issue."
}
trap _cleanup_on_error EXIT
echo "Creating packages/$PKG_NAME (${PASCAL_NAME}SdkApi)..."
echo ""
# ---- Step 1: flutter create ----
echo "[1/5] flutter create --template=plugin..."
cd "$ROOT_DIR"
flutter create \
--template=plugin \
--platforms=ios,android,macos,windows \
--org com.example \
--project-name "$PKG_NAME" \
"packages/$PKG_NAME"
echo ""
# ---- Step 2: mason make ----
echo "[2/5] mason make clean_plugin_sdk (Facade+Wiring)..."
cd "$ROOT_DIR"
$MASON_CMD make clean_plugin_sdk \
--name "$PKG_NAME" \
--sdk_class "${PASCAL_NAME}Sdk" \
--output-dir "packages/$PKG_NAME" \
--on-conflict overwrite
echo ""
# ---- Step 3: 修正 pubspec.yaml ----
echo "[3/5] Patching pubspec.yaml..."
python3 - "$PKG_DIR/pubspec.yaml" "$ROOT_DIR" << 'PY'
import sys, re, os, glob
path, root_dir = sys.argv[1], sys.argv[2]
with open(path) as f:
content = f.read()
# 从根 pubspec.yaml 读取当前 Dart 版本约束
dart_version = '3.11.0'
root_pubspec = os.path.join(root_dir, 'pubspec.yaml')
with open(root_pubspec) as f:
root_content = f.read()
m = re.search(r'^\s*sdk:\s*\^([\d.]+)', root_content, re.MULTILINE)
if m:
dart_version = m.group(1)
# 从已有 plugin 包读取当前 Flutter 最低版本(取最大值,避免旧包的低版本干扰)
def _ver(s):
try:
return tuple(int(x) for x in s.split('.'))
except Exception:
return (0,)
flutter_version = '3.38.4'
for pkg_pubspec in glob.glob(os.path.join(root_dir, 'packages', '*', 'pubspec.yaml')):
with open(pkg_pubspec) as f:
pkg_content = f.read()
m = re.search(r"flutter:\s*'>=([^']+)'", pkg_content)
if m and _ver(m.group(1)) > _ver(flutter_version):
flutter_version = m.group(1)
# 1. 注入 resolution: workspace
# flutter create 新版不含 publish_to按 publish_to → version → name 顺序降级匹配
if 'resolution: workspace' not in content:
if re.search(r'publish_to:[^\n]*\n', content):
content = re.sub(
r'(publish_to:[^\n]*\n)',
r'\1resolution: workspace\n',
content,
)
elif re.search(r'^version:[^\n]*\n', content, re.MULTILINE):
content = re.sub(
r'(^version:[^\n]*\n)',
r'\1resolution: workspace\n',
content,
flags=re.MULTILINE,
)
else:
content = re.sub(
r'(^name:[^\n]*\n)',
r'\1resolution: workspace\n',
content,
flags=re.MULTILINE,
)
# 2. 统一 Dart SDK 约束:'>=x.x.x <4.0.0' 或 ^x.x.x → 与主工程一致
# flutter create 新版直接生成 ^x.x.x旧版生成 '>=x <y',两种格式都处理
content = re.sub(r"sdk:\s*(?:['\"]>=[\d.]+ <[\d.]+['\"]|\^[\d.]+)", f"sdk: ^{dart_version}", content)
# 3. 统一 Flutter 最低版本:与主工程已有包一致
content = re.sub(r'flutter:\s*["\']>=[\d.]+["\']', f"flutter: '>={flutter_version}'", content)
# 4. 统一 flutter_lints 版本
content = re.sub(r"flutter_lints:\s*\^[\d.]+", "flutter_lints: ^6.0.0", content)
# 5. 注入代码生成依赖gen / gen:watch 靠 --depends-on="build_runner" 过滤,
# 没有 build_runner 的包会被跳过,@JsonSerializable / @freezed 等注解不会生成)
code_gen_deps = {
'build_runner': '^2.4.6',
'json_serializable': '^6.7.1',
'freezed': '^3.0.0',
}
# 从根 pubspec 读取实际版本(保持全局一致)
for dep, default_ver in code_gen_deps.items():
m = re.search(rf'^\s+{re.escape(dep)}:\s*(\^[\d.]+)', root_content, re.MULTILINE)
if m:
code_gen_deps[dep] = m.group(1)
for dep, ver in code_gen_deps.items():
if dep not in content:
content = re.sub(
r'(dev_dependencies:\n)',
rf'\1 {dep}: {ver}\n',
content,
)
with open(path, 'w') as f:
f.write(content)
PY
# example/pubspec.yaml 同步统一 Dart 版本flutter create 生成的 example 有相同问题)
python3 - "$PKG_DIR/example/pubspec.yaml" "$ROOT_DIR" << 'PY'
import sys, re, os
path, root_dir = sys.argv[1], sys.argv[2]
if not os.path.exists(path):
sys.exit(0)
dart_version = '3.11.0'
root_pubspec = os.path.join(root_dir, 'pubspec.yaml')
with open(root_pubspec) as f:
root_content = f.read()
m = re.search(r'^\s*sdk:\s*\^([\d.]+)', root_content, re.MULTILINE)
if m:
dart_version = m.group(1)
with open(path) as f:
content = f.read()
content = re.sub(r"sdk:\s*(?:['\"]>=[\d.]+ <[\d.]+['\"]|\^[\d.]+)", f"sdk: ^{dart_version}", content)
with open(path, 'w') as f:
f.write(content)
PY
# ---- Step 4: IDE 配置 ----
echo "[4/5] Updating IDE config..."
# .iml 文件
cat > "$PKG_DIR/melos_${PKG_NAME}.iml" << 'IML_EOF'
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/build" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Dart Packages" level="project" />
<orderEntry type="library" name="Flutter Plugins" level="project" />
</component>
</module>
IML_EOF
# 根 pubspec.yaml workspace
python3 - "$ROOT_DIR/pubspec.yaml" "$PKG_NAME" << 'PY'
import sys
pubspec_path, pkg_name = sys.argv[1], sys.argv[2]
with open(pubspec_path) as f:
lines = f.readlines()
new_entry = f' - packages/{pkg_name}\n'
if new_entry in lines:
sys.exit(0)
last_pkg_idx = -1
in_workspace = False
for i, line in enumerate(lines):
stripped = line.strip()
if stripped == 'workspace:':
in_workspace = True
elif in_workspace and stripped.startswith('- packages/'):
last_pkg_idx = i
elif in_workspace and stripped and not stripped.startswith('-'):
break
if last_pkg_idx >= 0:
lines.insert(last_pkg_idx + 1, new_entry)
with open(pubspec_path, 'w') as f:
f.writelines(lines)
PY
# .idea/modules.xml
python3 - "$ROOT_DIR/.idea/modules.xml" "$PKG_NAME" << 'PY'
import sys
modules_path, pkg_name = sys.argv[1], sys.argv[2]
with open(modules_path) as f:
content = f.read()
entry = (
f'<module fileurl="file://$PROJECT_DIR$/packages/{pkg_name}/melos_{pkg_name}.iml"'
f' filepath="$PROJECT_DIR$/packages/{pkg_name}/melos_{pkg_name}.iml"/>'
)
if entry in content:
sys.exit(0)
content = content.replace(' </modules>', f'{entry}\n </modules>')
with open(modules_path, 'w') as f:
f.write(content)
PY
# ---- Step 5: dart pub get ----
echo ""
echo "[5/5] dart pub get..."
cd "$ROOT_DIR" && dart pub get
echo ""
echo "Done. packages/$PKG_NAME is ready."
echo ""
echo "Next steps:"
echo " 1. Define API in lib/src/presentation/facade/${PKG_NAME}_api.dart"
echo " 2. Add domain entities under lib/src/domain/entities/"
echo " 3. Implement in lib/src/presentation/wiring/${PKG_NAME}_api_impl.dart"