Files
customer-im-client-dev/scripts/new_sdk.sh
Cody 0ee2c8c63c 优化配置,修复 demo bug
1,network 框架完善
2,websocket 机制完善
3,设计文档整理到架构文档
4,脚本,配置完善
2026-03-07 19:29:18 +08:00

420 lines
13 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 3.5: 修正 podspec swift_version ----
# flutter create 默认生成 swift_version = '5.0',统一改为项目标准 6.2
for podspec in \
"$PKG_DIR/ios/${PKG_NAME}.podspec" \
"$PKG_DIR/macos/${PKG_NAME}.podspec"; do
if [[ -f "$podspec" ]]; then
sed -i '' "s/s.swift_version = '5.0'/s.swift_version = '6.2'/" "$podspec"
fi
done
# Flutter SDK 尚未完整标注 Swift 6 并发属性,用 @preconcurrency import 将
# FlutterMethodNotImplemented 等全局变量的并发警告降级,避免编译失败。
# 注意:不在类上加 @MainActor否则与 FlutterPlugin 协议的 nonisolated 要求冲突,
# 导致 ConformanceIsolation 编译错误。
for swift_file in \
"$PKG_DIR/ios/Classes/${PASCAL_NAME}SdkPlugin.swift" \
"$PKG_DIR/macos/Classes/${PASCAL_NAME}SdkPlugin.swift"; do
if [[ -f "$swift_file" ]]; then
sed -i '' \
's/^import Flutter$/@preconcurrency import Flutter/' \
"$swift_file"
fi
done
# ---- Step 3.6: 替换 Android build.gradle → build.gradle.ktsKotlin DSL + Compose ----
# flutter create 默认生成 Groovy build.gradle统一替换为 Kotlin DSL
ANDROID_DIR="$PKG_DIR/android"
[[ -f "$ANDROID_DIR/build.gradle" ]] && rm "$ANDROID_DIR/build.gradle"
cat > "$ANDROID_DIR/build.gradle.kts" << GRADLE_EOF
group = "com.example.${PKG_NAME}"
version = "1.0-SNAPSHOT"
buildscript {
val kotlinVersion = "2.2.20"
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.11.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:\$kotlinVersion")
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
// 若该 SDK 需要 Compose取消注释下面两行并在 dependencies 中加 compose runtime
// id("org.jetbrains.kotlin.plugin.compose")
}
android {
namespace = "com.example.${PKG_NAME}"
compileSdk = 36
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
sourceSets {
getByName("main") { java.srcDirs("src/main/kotlin") }
getByName("test") { java.srcDirs("src/test/kotlin") }
}
defaultConfig {
minSdk = 24
}
// 若该 SDK 需要 Compose取消注释
// buildFeatures { compose = true }
testOptions {
unitTests {
isIncludeAndroidResources = true
all {
it.useJUnitPlatform()
it.outputs.upToDateWhen { false }
it.testLogging {
events("passed", "skipped", "failed", "standardOut", "standardError")
showStandardStreams = true
}
}
}
}
}
kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
}
}
dependencies {
// 若该 SDK 需要 Compose取消注释以下两行并按需引入具体组件
// val composeBom = platform("androidx.compose:compose-bom:2025.05.01")
// implementation(composeBom)
// implementation("androidx.compose.runtime:runtime")
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.mockito:mockito-core:5.0.0")
}
GRADLE_EOF
# ---- 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"