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

67
scripts/build_android.sh Executable file
View File

@@ -0,0 +1,67 @@
#!/usr/bin/env bash
# Android Release 打包脚本
#
# Usage:
# bash scripts/build_android.sh apk # APK本地测试 / 内部分发)
# bash scripts/build_android.sh aab # AABGoogle Play 上架)
# bash scripts/build_android.sh apk prod # APK + prod 配置CI 使用)
# bash scripts/build_android.sh aab prod # AAB + prod 配置CI 使用)
#
# Via melos:
# melos run build:android:apk
# melos run build:android:aab
#
# 产物路径:
# APK → apps/im_app/build/app/outputs/flutter-apk/app-release.apk
# AAB → apps/im_app/build/app/outputs/bundle/release/app-release.aab
# 符号表 → apps/im_app/build/debug-info/android/(保留用于线上崩溃还原堆栈)
set -euo pipefail
FORMAT="${1:-apk}"
if [[ "$FORMAT" != "apk" && "$FORMAT" != "aab" ]]; then
echo "Error: first argument must be 'apk' or 'aab'" >&2
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
APP_DIR="$ROOT_DIR/apps/im_app"
CONFIG_FILE="$APP_DIR/config/config.json"
DEBUG_INFO_DIR="$APP_DIR/build/debug-info/android"
# ── 可选:写入 prod 配置 ────────────────────────────────────────────────────
if [ "${2:-}" = "prod" ]; then
echo "==> Writing prod config..."
cat > "$CONFIG_FILE" <<EOF
{
"IS_DEV": false,
"API_BASE_URL": "${PROD_API_BASE_URL:?PROD_API_BASE_URL is not set}"
}
EOF
fi
# ── 打包 ────────────────────────────────────────────────────────────────────
if [ "$FORMAT" = "aab" ]; then
echo "==> Building Android release AAB (Google Play)..."
cd "$APP_DIR"
flutter build appbundle \
--release \
--dart-define-from-file=config/config.json \
--split-debug-info="$DEBUG_INFO_DIR" \
--obfuscate
echo ""
echo "✓ AAB: $APP_DIR/build/app/outputs/bundle/release/app-release.aab"
else
echo "==> Building Android release APK..."
cd "$APP_DIR"
flutter build apk \
--release \
--dart-define-from-file=config/config.json \
--split-debug-info="$DEBUG_INFO_DIR" \
--obfuscate
echo ""
echo "✓ APK: $APP_DIR/build/app/outputs/flutter-apk/app-release.apk"
fi
echo "✓ 符号表: $DEBUG_INFO_DIR(请妥善保存,用于线上崩溃堆栈还原)"

50
scripts/build_ios.sh Normal file
View File

@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# iOS Release IPA 打包脚本
#
# Usage:
# bash scripts/build_ios.sh # 使用默认 configdev 配置)
# bash scripts/build_ios.sh prod # 写入 prod 配置后打包CI 使用)
#
# Via melos:
# melos run build:ios
#
# 产物路径:
# IPA → apps/im_app/build/ios/ipa/im_app.ipa
# 符号表 → apps/im_app/build/debug-info/ios/(保留用于线上崩溃还原堆栈)
#
# 前置条件:
# - macOS 环境,已安装 Xcode
# - 已配置签名证书Release scheme 需绑定正式 Provisioning Profile
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
APP_DIR="$ROOT_DIR/apps/im_app"
CONFIG_FILE="$APP_DIR/config/config.json"
DEBUG_INFO_DIR="$APP_DIR/build/debug-info/ios"
# ── 可选:写入 prod 配置 ────────────────────────────────────────────────────
if [ "${1:-}" = "prod" ]; then
echo "==> Writing prod config..."
cat > "$CONFIG_FILE" <<EOF
{
"IS_DEV": false,
"API_BASE_URL": "${PROD_API_BASE_URL:?PROD_API_BASE_URL is not set}"
}
EOF
fi
# ── 打包 ────────────────────────────────────────────────────────────────────
echo "==> Building iOS release IPA..."
cd "$APP_DIR"
flutter build ipa \
--release \
--dart-define-from-file=config/config.json \
--split-debug-info="$DEBUG_INFO_DIR" \
--obfuscate
echo ""
echo "✓ IPA: $APP_DIR/build/ios/ipa/im_app.ipa"
echo "✓ 符号表: $DEBUG_INFO_DIR(请妥善保存,用于线上崩溃堆栈还原)"

46
scripts/build_macos.sh Normal file
View File

@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# macOS Release App 打包脚本
#
# Usage:
# bash scripts/build_macos.sh # 使用默认 configdev 配置)
# bash scripts/build_macos.sh prod # 写入 prod 配置后打包CI 使用)
#
# Via melos:
# melos run build:macos
#
# 产物路径:
# .app → apps/im_app/build/macos/Build/Products/Release/im_app.app
# 符号表 → apps/im_app/build/debug-info/macos/
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
APP_DIR="$ROOT_DIR/apps/im_app"
CONFIG_FILE="$APP_DIR/config/config.json"
DEBUG_INFO_DIR="$APP_DIR/build/debug-info/macos"
# ── 可选:写入 prod 配置 ────────────────────────────────────────────────────
if [ "${1:-}" = "prod" ]; then
echo "==> Writing prod config..."
cat > "$CONFIG_FILE" <<EOF
{
"IS_DEV": false,
"API_BASE_URL": "${PROD_API_BASE_URL:?PROD_API_BASE_URL is not set}"
}
EOF
fi
# ── 打包 ────────────────────────────────────────────────────────────────────
echo "==> Building macOS release app..."
cd "$APP_DIR"
flutter build macos \
--release \
--dart-define-from-file=config/config.json \
--split-debug-info="$DEBUG_INFO_DIR" \
--obfuscate
echo ""
echo "✓ App: $APP_DIR/build/macos/Build/Products/Release/im_app.app"
echo "✓ 符号表: $DEBUG_INFO_DIR(请妥善保存,用于线上崩溃堆栈还原)"

48
scripts/build_windows.sh Normal file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# Windows Release 打包脚本
#
# Usage:
# bash scripts/build_windows.sh # 使用默认 configdev 配置)
# bash scripts/build_windows.sh prod # 写入 prod 配置后打包CI 使用)
#
# Via melos:
# melos run build:windows
#
# 产物路径:
# EXE → apps/im_app/build/windows/x64/runner/Release/
# 符号表 → apps/im_app/build/debug-info/windows/
#
# 注意:需在 Windows 环境下运行(或 Windows CI Runner
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
APP_DIR="$ROOT_DIR/apps/im_app"
CONFIG_FILE="$APP_DIR/config/config.json"
DEBUG_INFO_DIR="$APP_DIR/build/debug-info/windows"
# ── 可选:写入 prod 配置 ────────────────────────────────────────────────────
if [ "${1:-}" = "prod" ]; then
echo "==> Writing prod config..."
cat > "$CONFIG_FILE" <<EOF
{
"IS_DEV": false,
"API_BASE_URL": "${PROD_API_BASE_URL:?PROD_API_BASE_URL is not set}"
}
EOF
fi
# ── 打包 ────────────────────────────────────────────────────────────────────
echo "==> Building Windows release..."
cd "$APP_DIR"
flutter build windows \
--release \
--dart-define-from-file=config/config.json \
--split-debug-info="$DEBUG_INFO_DIR" \
--obfuscate
echo ""
echo "✓ EXE: $APP_DIR/build/windows/x64/runner/Release/"
echo "✓ 符号表: $DEBUG_INFO_DIR(请妥善保存,用于线上崩溃堆栈还原)"

77
scripts/bump_sdk.sh Executable file
View File

@@ -0,0 +1,77 @@
#!/bin/bash
# 批量升级 Dart / Flutter SDK 版本约束
#
# 无参数从官方拉最新稳定版Dart + Flutter 一起升级:
# melos run sdk:bump
#
# 只升 DartFlutter 下限不变):
# melos run sdk:bump -- --dart 3.12.0
#
# 只升 Flutter 下限Dart 不变):
# melos run sdk:bump -- --flutter 3.40.0
#
# 手动指定两者CI 固定版本):
# melos run sdk:bump -- --dart 3.12.0 --flutter 3.40.0
set -euo pipefail
DART_VERSION=""
FLUTTER_VERSION=""
while [[ $# -gt 0 ]]; do
case "$1" in
--dart) DART_VERSION="$2"; shift 2 ;;
--flutter) FLUTTER_VERSION="$2"; shift 2 ;;
*) echo "Unknown argument: $1"; exit 1 ;;
esac
done
# 无参数从官方接口获取最新稳定版本Dart + Flutter 一起升级
if [[ -z "$DART_VERSION" ]] && [[ -z "$FLUTTER_VERSION" ]]; then
OS_TYPE="macos"
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
OS_TYPE="linux"
fi
RELEASES_URL="https://storage.googleapis.com/flutter_infra_release/releases/releases_${OS_TYPE}.json"
echo "Fetching latest stable Flutter version from flutter.dev..."
RELEASES_JSON=$(curl -sf "$RELEASES_URL") || {
echo "Error: Failed to fetch Flutter releases from $RELEASES_URL"
exit 1
}
read -r FLUTTER_VERSION DART_VERSION <<< "$(echo "$RELEASES_JSON" | python3 - <<'PY'
import json, sys, re
d = json.load(sys.stdin)
stable_hash = d["current_release"]["stable"]
release = next(r for r in d["releases"] if r["hash"] == stable_hash)
flutter_ver = release["version"].lstrip("v")
dart_ver = re.search(r"[\d]+\.[\d]+\.[\d]+", release["dart_sdk_version"]).group()
print(flutter_ver, dart_ver)
PY
)"
fi
PUBSPECS=$(find . \
-name "pubspec.yaml" \
! -path "*/.dart_tool/*" \
! -path "*/build/*" \
! -path "*/.pub-cache/*")
if [[ -n "$DART_VERSION" ]]; then
echo "Dart SDK : ^$DART_VERSION"
echo "$PUBSPECS" | xargs sed -i '' \
"s/sdk: \^[0-9][0-9.]*[0-9]/sdk: ^$DART_VERSION/g"
fi
if [[ -n "$FLUTTER_VERSION" ]]; then
echo "Flutter SDK : >=$FLUTTER_VERSION"
echo "$PUBSPECS" | xargs sed -i '' \
"s/flutter: '>=[0-9][0-9.]*[0-9]'/flutter: '>=$FLUTTER_VERSION'/g"
fi
echo ""
echo "Running dart pub get..."
dart pub get
echo "Done."

114
scripts/clean.sh Executable file
View File

@@ -0,0 +1,114 @@
#!/usr/bin/env bash
# Deep clean script — supports platform filtering
#
# Usage:
# bash scripts/clean.sh # clean all platforms (default)
# bash scripts/clean.sh android # clean Android only
# bash scripts/clean.sh ios # clean iOS only
# bash scripts/clean.sh macos # clean macOS only
# bash scripts/clean.sh windows # clean Windows only
# bash scripts/clean.sh android ios # clean multiple platforms
#
# Via melos:
# melos run clean:deep # all platforms
# melos run clean:deep -- android # Android only
# melos run clean:deep -- ios macos # iOS + macOS
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
APP_DIR="$ROOT_DIR/apps/im_app"
# ── Parse platforms ────────────────────────────────────────────────────────
ALL_PLATFORMS=(android ios macos windows)
if [ $# -eq 0 ]; then
TARGETS=("${ALL_PLATFORMS[@]}")
else
TARGETS=("$@")
fi
should_clean() {
local platform="$1"
for t in "${TARGETS[@]}"; do
[[ "$t" == "$platform" ]] && return 0
done
return 1
}
echo "==> Platforms: ${TARGETS[*]}"
# ── Flutter & Dart (always runs) ───────────────────────────────────────────
echo "==> flutter clean..."
cd "$APP_DIR"
flutter clean
echo "==> Removing .dart_tool directories..."
while IFS= read -r -d '' dir; do
echo " rm -rf $dir"
rm -rf "$dir"
done < <(find "$ROOT_DIR" -type d -name ".dart_tool" -not -path "*/.git/*" -print0)
echo "==> Removing generated Dart files (*.g.dart / *.freezed.dart / *.mocks.dart)..."
while IFS= read -r -d '' file; do
echo " rm $file"
rm "$file"
done < <(find "$ROOT_DIR" -type f \
\( -name "*.g.dart" -o -name "*.freezed.dart" -o -name "*.mocks.dart" \) \
-not -path "*/.git/*" -not -path "*/build/*" -print0)
# ── Android ────────────────────────────────────────────────────────────────
if should_clean android; then
echo "==> Cleaning Android Gradle..."
cd "$APP_DIR/android"
chmod +x gradlew
./gradlew clean
fi
# ── iOS ────────────────────────────────────────────────────────────────────
if should_clean ios; then
IOS_DIR="$APP_DIR/ios"
if [ -d "$IOS_DIR" ]; then
echo "==> Cleaning iOS Pods..."
rm -rf "$IOS_DIR/Pods"
rm -f "$IOS_DIR/Podfile.lock"
rm -rf "$IOS_DIR/.symlinks"
if command -v pod &>/dev/null; then
cd "$IOS_DIR" && pod cache clean --all
fi
fi
fi
# ── macOS ──────────────────────────────────────────────────────────────────
if should_clean macos; then
MACOS_DIR="$APP_DIR/macos"
if [ -d "$MACOS_DIR" ]; then
echo "==> Cleaning macOS Pods..."
rm -rf "$MACOS_DIR/Pods"
rm -f "$MACOS_DIR/Podfile.lock"
if command -v pod &>/dev/null; then
cd "$MACOS_DIR" && pod cache clean --all
fi
fi
fi
# ── Windows ────────────────────────────────────────────────────────────────
if should_clean windows; then
WIN_DIR="$APP_DIR/windows"
if [ -d "$WIN_DIR" ]; then
echo "==> Cleaning Windows build artifacts..."
rm -rf "$WIN_DIR/flutter/ephemeral"
find "$WIN_DIR" -name "CMakeCache.txt" -delete
find "$WIN_DIR" -name "CMakeFiles" -type d -exec rm -rf {} + 2>/dev/null || true
fi
fi
# ── Re-fetch dependencies ──────────────────────────────────────────────────
echo "==> Running dart pub get at workspace root..."
cd "$ROOT_DIR"
dart pub get
echo ""
echo "Deep clean complete. Platforms cleaned: ${TARGETS[*]}"
echo "Next: melos run gen"

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"

104
scripts/remove_sdk.sh Executable file
View File

@@ -0,0 +1,104 @@
#!/bin/bash
# 删除 SDK 包,同步清理 workspace、IDE 模块注册、各包依赖引用
#
# 用法:
# melos run remove:sdk -- push # 删除 packages/push_sdk/
# melos run remove:sdk -- push_sdk # 同上
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
if [[ $# -eq 0 ]]; then
echo "Usage: melos run remove:sdk -- <name>"
echo "Example: melos run remove:sdk -- push"
exit 1
fi
INPUT="$1"
BASE_NAME="${INPUT%_sdk}"
PKG_NAME="${BASE_NAME}_sdk"
PKG_DIR="$ROOT_DIR/packages/$PKG_NAME"
if [[ ! -d "$PKG_DIR" ]]; then
echo "Warning: packages/$PKG_NAME not found. Will still clean up config references."
read -r -p "Continue cleanup? [y/N] " REPLY
else
echo "About to permanently delete packages/$PKG_NAME and clean up all references."
read -r -p "Confirm? [y/N] " REPLY
fi
if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then
echo "Aborted."
exit 0
fi
# ---- 删除包目录 ----
if [[ -d "$PKG_DIR" ]]; then
rm -rf "$PKG_DIR"
echo "Removed packages/$PKG_NAME"
else
echo "Skipped directory removal (already gone)"
fi
# ---- 更新根 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()
entry = f' - packages/{pkg_name}\n'
lines = [l for l in lines if l != entry]
with open(pubspec_path, 'w') as f:
f.writelines(lines)
PY
echo "Updated pubspec.yaml workspace"
# ---- 更新 .idea/modules.xml ----
python3 - "$ROOT_DIR/.idea/modules.xml" "$PKG_NAME" << 'PY'
import sys, re
modules_path, pkg_name = sys.argv[1], sys.argv[2]
with open(modules_path) as f:
content = f.read()
pattern = rf'[ \t]*<module[^>]*packages/{re.escape(pkg_name)}/[^>]*/>\n'
content = re.sub(pattern, '', content)
with open(modules_path, 'w') as f:
f.write(content)
PY
echo "Updated .idea/modules.xml"
# ---- 扫描并移除其他包中的依赖引用 ----
python3 - "$ROOT_DIR" "$PKG_NAME" << 'PY'
import sys, os, re, glob
root_dir, pkg_name = sys.argv[1], sys.argv[2]
pubspecs = glob.glob(os.path.join(root_dir, 'apps', '*', 'pubspec.yaml')) + \
glob.glob(os.path.join(root_dir, 'packages', '*', 'pubspec.yaml'))
pattern = re.compile(rf'^\s+{re.escape(pkg_name)}\s*:.*\n', re.MULTILINE)
for path in pubspecs:
with open(path) as f:
content = f.read()
updated = pattern.sub('', content)
if updated != content:
with open(path, 'w') as f:
f.write(updated)
rel = os.path.relpath(path, root_dir)
print(f' Removed {pkg_name} from {rel}')
PY
echo "Scanned dependency references"
# ---- dart pub get ----
echo ""
cd "$ROOT_DIR" && dart pub get
echo ""
echo "Done. packages/$PKG_NAME removed."

95
scripts/setup.sh Executable file
View File

@@ -0,0 +1,95 @@
#!/bin/bash
# 新机器环境初始化(只需执行一次)
#
# 用法:在项目根目录执行
# bash scripts/setup.sh
#
# 完成后重启终端(或 source ~/.zshrc使 PATH 全局生效。
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
PUB_CACHE_BIN="$HOME/.pub-cache/bin"
# 前置检查Flutter SDK 必须已安装
if ! command -v dart &> /dev/null; then
echo "Error: dart not found."
echo "Please install Flutter SDK first: https://docs.flutter.dev/get-started/install"
exit 1
fi
if ! command -v flutter &> /dev/null; then
echo "Error: flutter not found."
echo "Please install Flutter SDK first: https://docs.flutter.dev/get-started/install"
exit 1
fi
echo "=== customer-im-client 环境初始化 ==="
echo ""
# ---- Step 1: 安装全局工具 ----
echo "[1/6] 安装全局工具 (melos + mason_cli)..."
dart pub global activate melos
dart pub global activate mason_cli
echo ""
# ---- Step 2: 配置 PATH ----
echo "[2/6] 配置 PATH..."
if [[ "$SHELL" == */zsh ]]; then
SHELL_CONFIG="$HOME/.zshrc"
elif [[ "$SHELL" == */bash ]]; then
SHELL_CONFIG="$HOME/.bash_profile"
else
SHELL_CONFIG="$HOME/.profile"
fi
PATH_LINE='export PATH="$PATH":"$HOME/.pub-cache/bin"'
if ! grep -qF ".pub-cache/bin" "$SHELL_CONFIG" 2>/dev/null; then
echo "$PATH_LINE" >> "$SHELL_CONFIG"
echo " 写入 PATH 到 $SHELL_CONFIG"
else
echo " PATH 已配置,跳过"
fi
export PATH="$PATH:$PUB_CACHE_BIN" # 当前 session 立即生效
echo ""
# ---- Step 3: 安装项目依赖 ----
echo "[3/6] dart pub get..."
cd "$ROOT_DIR" && dart pub get
echo ""
# ---- Step 4: IDE 模块配置生成 ----
# 生成 .iml 和 .idea/modules.xmlIDE 靠这些文件识别各个包并显示 [melos_xxx] 标签。
# 这些文件在 .gitignore 中不入库,每台机器需要本地生成。
# 已有环境缺失标签时,单独执行 melos bootstrap 即可补全,无需重跑整个 setup。
echo "[4/6] melos bootstrap (生成 .iml + modules.xml)..."
cd "$ROOT_DIR" && "$PUB_CACHE_BIN/melos" bootstrap
echo ""
# ---- Step 4.5: 清理旧的 mason 缓存 ----
if [ -d "$ROOT_DIR/.mason" ]; then
echo " 清理旧 .mason 目录..."
rm -rf "$ROOT_DIR/.mason"
fi
if [ -f "$ROOT_DIR/mason-lock.json" ]; then
echo " 清理旧 mason-lock.json..."
rm -f "$ROOT_DIR/mason-lock.json"
fi
# ---- Step 5: 注册 mason bricks ----
echo "[5/6] mason get (注册本地 bricks)..."
cd "$ROOT_DIR" && "$PUB_CACHE_BIN/mason" get
echo ""
# ---- Step 6: 首次代码生成 ----
echo "[6/6] melos run gen (首次代码生成)..."
cd "$ROOT_DIR" && "$PUB_CACHE_BIN/melos" run gen
echo ""
# ---- 验证 ----
echo "=== 验证 ==="
echo -n "melos : " && "$PUB_CACHE_BIN/melos" --version
echo -n "mason : " && "$PUB_CACHE_BIN/mason" --version
echo ""
echo "=== 完成!重启终端(或执行 source ${SHELL_CONFIG})使 PATH 全局生效 ==="