Initial project
This commit is contained in:
67
scripts/build_android.sh
Executable file
67
scripts/build_android.sh
Executable 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 # AAB(Google 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
50
scripts/build_ios.sh
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# iOS Release IPA 打包脚本
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/build_ios.sh # 使用默认 config(dev 配置)
|
||||
# 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
46
scripts/build_macos.sh
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
# macOS Release App 打包脚本
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/build_macos.sh # 使用默认 config(dev 配置)
|
||||
# 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
48
scripts/build_windows.sh
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
# Windows Release 打包脚本
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/build_windows.sh # 使用默认 config(dev 配置)
|
||||
# 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
77
scripts/bump_sdk.sh
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/bin/bash
|
||||
# 批量升级 Dart / Flutter SDK 版本约束
|
||||
#
|
||||
# 无参数:从官方拉最新稳定版,Dart + Flutter 一起升级:
|
||||
# melos run sdk:bump
|
||||
#
|
||||
# 只升 Dart(Flutter 下限不变):
|
||||
# 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
114
scripts/clean.sh
Executable 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
308
scripts/new_sdk.sh
Executable 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
104
scripts/remove_sdk.sh
Executable 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
95
scripts/setup.sh
Executable 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.xml,IDE 靠这些文件识别各个包并显示 [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 全局生效 ==="
|
||||
Reference in New Issue
Block a user