feat: N1d/N2/N3 + قانون فلاتر وإعادة ضبط تطبيق السائق
الباك إند: - تعيين أول أدمن صار يُنشئ المستخدم إن لم يوجد — كان يشترط دخولاً سابقاً، وهي بيضة ودجاجة تمنع الدخول إلى لوحة أي مستأجر جديد أصلاً. - OTP_DEV_MODE كان يفشل مفتوحاً (`!== 'false'`): غياب المتغيّر أو خطأ مطبعي يترك الإنتاج برمز ثابت يفتح كل حساب، ويحرس السحب المالي كذلك. صار `=== 'true'` في الإعداد وفي قارئَيه، مع تحذير عند الإقلاع. - N2: التحقّق من اللوغو عند الرفع (PNG/JPEG · 512+ · مربّع · سقف 5MB) عبر قراءة الترويسة بلا اعتمادية — بدل اكتشاف أيقونة ممطوطة بعد النشر. N3: السكربت يولّد build_config.dart بأعلام const (طبقات docs/22 §1.5)، ويضبط bundle IDs واسم التطبيق والأيقونات/splash، و--build يشغّل Shorebird. فلاتر: - docs/23: قانون مُلزِم للتطبيقين (البنية · Cubit · طبقة الشبكة · الأعلام). - السائق: أُزيل Dart القديم (GetX) مع الإبقاء على الأصيل والإضافات وشهادات التوقيع وShorebird وFirebase الخاص به، وأُعيد هيكلته مطابقاً للراكب. - طبقة الشبكة ترسل x-device-id وتجدّد التوكن عند 401 مرة واحدة فقط. - الحزم موحّدة بين التطبيقين، والـAPI https حصراً في الاثنين. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
3ab74a13a8
commit
de4bbd00ac
+145
-36
@@ -2,38 +2,44 @@
|
||||
# ==========================================================================
|
||||
# توليد تطبيق مستأجر واحد من مانيفست الباك إند (docs/22 — N3).
|
||||
#
|
||||
# سكربت واحد يأخذ slug المستأجر → يجلب مانيفسته → يضبط bundle ID والأيقونات
|
||||
# والـsplash من اللوغو، يحقن FCM، يربط إضافات Kotlin/C++ الأصيلة (overlay ·
|
||||
# method channels · NDK) المبنية على bundle ID، يبني، ويسجّل/يدفع عبر Shorebird.
|
||||
# سكربت واحد يأخذ slug المستأجر → يجلب مانيفسته → يولّد BuildConfig بأعلام
|
||||
# `const` (طبقات lite/pro/max — docs/22 §1.5) → يضبط bundle ID والأيقونات
|
||||
# والـsplash من اللوغو → يبني ويدفع عبر Shorebird.
|
||||
#
|
||||
# الاستعمال:
|
||||
# PLATFORM_SECRET=xxx API=https://tripz-api.intaleqapp.com/api \
|
||||
# ./scripts/generate-tenant-app.sh <slug> <rider|driver>
|
||||
# ./scripts/generate-tenant-app.sh <slug> <rider|driver> [--build]
|
||||
#
|
||||
# ⚠️ الخطوات الموسومة [Q] تعتمد على مشروع فلاتر (المجموعة Q) الذي لم يُبنَ بعد.
|
||||
# البنية والعقد (المانيفست) جاهزان الآن؛ تُوصَل هذه الخطوات عند وصول Q.
|
||||
# بلا `--build` يتوقّف بعد التوليد (تفتيشٌ بلا بناء) — افتراض متعمَّد: البناء
|
||||
# يستهلك دقائق ويحتاج بيئة فلاتر كاملة، فلا يُطلق ضمناً.
|
||||
#
|
||||
# ⚠️ الخطوات الموسومة [Q] تعتمد على مشروع فلاتر (المجموعة Q) غير المكتمل بعد.
|
||||
# ==========================================================================
|
||||
set -euo pipefail
|
||||
|
||||
SLUG="${1:?الاستعمال: generate-tenant-app.sh <slug> <rider|driver>}"
|
||||
SLUG="${1:?الاستعمال: generate-tenant-app.sh <slug> <rider|driver> [--build]}"
|
||||
APP_KIND="${2:-rider}" # rider | driver
|
||||
DO_BUILD="${3:-}"
|
||||
API="${API:?اضبط API=https://tripz-api.intaleqapp.com/api}"
|
||||
SECRET="${PLATFORM_SECRET:?اضبط PLATFORM_SECRET}"
|
||||
|
||||
case "$APP_KIND" in
|
||||
rider|driver) ;;
|
||||
*) echo "✗ نوع التطبيق يجب أن يكون rider أو driver (وصل: $APP_KIND)"; exit 1 ;;
|
||||
esac
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
APP_DIR="$ROOT/apps/$APP_KIND" # [Q] مشروع فلاتر لكل نوع
|
||||
APP_DIR="$ROOT/apps/$APP_KIND"
|
||||
WORK="$ROOT/.build/$SLUG-$APP_KIND"
|
||||
|
||||
echo "▶ توليد تطبيق [$APP_KIND] للمستأجر [$SLUG]"
|
||||
|
||||
# ── 1. جلب المانيفست من الباك إند (جاهز الآن) ──────────────────────────
|
||||
# ── 1. جلب المانيفست من الباك إند ──────────────────────────────────────
|
||||
echo " • جلب المانيفست…"
|
||||
# نحصل على tenant id من القائمة ثم المانيفست
|
||||
TENANT_ID="$(curl -fsSL -H "x-platform-secret: $SECRET" "$API/admin/tenants" \
|
||||
| node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const t=JSON.parse(s).find(x=>x.slug==='$SLUG');if(!t){console.error('مستأجر غير موجود: $SLUG');process.exit(1)}process.stdout.write(t.id)})")"
|
||||
|
||||
MANIFEST="$(curl -fsSL -H "x-platform-secret: $SECRET" "$API/admin/tenants/$TENANT_ID/app-manifest")"
|
||||
echo "$MANIFEST" | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const m=JSON.parse(s);console.log(' الاسم:',m.app_name,'| bundleAndroid:',m.bundle_id_android,'| دولة:',m.country_pack)})"
|
||||
|
||||
val() { echo "$MANIFEST" | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{process.stdout.write(String(JSON.parse(s).$1 ?? ''))})"; }
|
||||
APP_NAME="$(val app_name)"
|
||||
@@ -41,38 +47,141 @@ BID_ANDROID="$(val bundle_id_android)"
|
||||
BID_IOS="$(val bundle_id_ios)"
|
||||
COUNTRY="$(val country_pack)"
|
||||
|
||||
[ -z "$BID_ANDROID" ] && { echo " ✗ bundle_id_android غير مضبوط في المانيفست — اضبطه من لوحة السوبر-أدمن أولاً"; exit 1; }
|
||||
echo " الاسم: $APP_NAME | أندرويد: $BID_ANDROID | دولة: $COUNTRY"
|
||||
[ -z "$BID_ANDROID" ] && { echo " ✗ bundle_id_android غير مضبوط — اضبطه من لوحة السوبر-أدمن أولاً"; exit 1; }
|
||||
|
||||
# ── 2. تحضير نسخة عمل من مشروع فلاتر ───────────────────────────────────
|
||||
if [ ! -d "$APP_DIR" ]; then
|
||||
echo " ⚠️ [Q] مشروع فلاتر $APP_DIR غير موجود بعد — تخطّي خطوات البناء."
|
||||
echo " المانيفست جاهز؛ تُوصَل خطوات فلاتر عند بناء المجموعة Q."
|
||||
# ── 2. نسخة عمل من مشروع فلاتر ─────────────────────────────────────────
|
||||
if [ ! -d "$APP_DIR/lib" ]; then
|
||||
echo " ⚠️ [Q] مشروع فلاتر $APP_DIR غير جاهز بعد — أحفظ المانيفست فقط."
|
||||
mkdir -p "$ROOT/.build"
|
||||
echo "$MANIFEST" > "$ROOT/.build/$SLUG-$APP_KIND.manifest.json"
|
||||
echo "✅ حُفظ المانيفست في .build/$SLUG-$APP_KIND.manifest.json"
|
||||
exit 0
|
||||
fi
|
||||
rm -rf "$WORK"; mkdir -p "$(dirname "$WORK")"; cp -r "$APP_DIR" "$WORK"
|
||||
|
||||
# ── 3. ضبط bundle ID (أندرويد + iOS) ───────────────────────────────────
|
||||
# ── 3. توليد BuildConfig بأعلام const ⭐ (docs/22 §1.5) ─────────────────
|
||||
# جوهر الطبقات: علم `const` قيمته false يُحذف كوده من الـbinary عند بناء AOT
|
||||
# (tree-shaking). لو قرأنا الأعلام من Map وقت التشغيل بقي كود كل الميزات في
|
||||
# الملف وأمكن استخراجه — وضاع الغرض كله.
|
||||
echo " • توليد BuildConfig بأعلام const…"
|
||||
mkdir -p "$WORK/lib/core"
|
||||
echo "$MANIFEST" | node -e "
|
||||
let s=''; process.stdin.on('data',d=>s+=d).on('end',()=>{
|
||||
const m = JSON.parse(s);
|
||||
const q = (v) => JSON.stringify(v ?? '');
|
||||
const feats = m.features || {};
|
||||
const camel = (k) => k.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
||||
const flags = Object.keys(feats).sort()
|
||||
.map(k => ' static const bool ' + camel(k) + ' = ' + (feats[k] === true) + ';')
|
||||
.join('\n');
|
||||
const lim = m.limits || {};
|
||||
|
||||
const out = [
|
||||
'// ⚠️ ملف مُولَّد — لا تُحرّره. مصدره scripts/generate-tenant-app.sh',
|
||||
'// من مانيفست الباك إند. الأعلام const عمداً: قيمة false يُحذف كودها من',
|
||||
'// الـbinary عند بناء AOT (docs/22 §1.5). لا تحوّلها إلى Map وقت تشغيل.',
|
||||
'',
|
||||
'class BuildConfig {',
|
||||
' static const String tenantSlug = ' + q(m.slug) + ';',
|
||||
' static const String tenantName = ' + q(m.name) + ';',
|
||||
' static const String appName = ' + q(m.app_name) + ';',
|
||||
' static const String countryPack = ' + q(m.country_pack) + ';',
|
||||
' static const String bundleIdAndroid = ' + q(m.bundle_id_android) + ';',
|
||||
' static const String bundleIdIos = ' + q(m.bundle_id_ios) + ';',
|
||||
'',
|
||||
' /// وسائل الدفع المبنيّة. المفعّل فعلياً = هذه ∩ المانيفست الريموت.',
|
||||
' static const List<String> paymentMethods = ' + JSON.stringify(m.payment_methods || []) + ';',
|
||||
'',
|
||||
' /// الحدود العددية — للعرض فقط؛ الفرض على السيرفر (docs/19 K4).',
|
||||
' static const int driversMax = ' + (lim.drivers_max || 0) + ';',
|
||||
' static const int citiesMax = ' + (lim.cities_max || 0) + ';',
|
||||
'}',
|
||||
'',
|
||||
'/// أعلام الميزات المخبوزة في هذا البناء.',
|
||||
'/// الفعّال = هذه ∩ المانيفست الريموت ∩ استحقاقات السيرفر (docs/22 §1.5).',
|
||||
'class Features {',
|
||||
flags,
|
||||
'}',
|
||||
'',
|
||||
].join('\n');
|
||||
process.stdout.write(out);
|
||||
});
|
||||
" > "$WORK/lib/core/build_config.dart"
|
||||
echo " ✓ lib/core/build_config.dart"
|
||||
|
||||
# ── 4. ضبط bundle ID (أندرويد + iOS) ───────────────────────────────────
|
||||
echo " • ضبط bundle ID…"
|
||||
# [Q] applicationId في build.gradle، و PRODUCT_BUNDLE_IDENTIFIER في iOS.
|
||||
# الإضافات الأصيلة (overlay/NDK/method-channel) مبنية على bundle ID —
|
||||
# تُحدَّث مراجعها هنا. راجع apps/*/android و apps/*/ios.
|
||||
# sed -i "s/applicationId \".*\"/applicationId \"$BID_ANDROID\"/" "$WORK/android/app/build.gradle"
|
||||
GRADLE="$WORK/android/app/build.gradle"
|
||||
[ -f "$GRADLE.kts" ] && GRADLE="$GRADLE.kts"
|
||||
if [ -f "$GRADLE" ]; then
|
||||
# sed -i يختلف بين BSD (ماك) وGNU (لينكس) — نمرّر لاحقة نسخ احتياطي صراحةً
|
||||
# ثم نحذفها، وهي الصيغة الوحيدة التي تعمل على الاثنين.
|
||||
sed -i.bak -E "s/applicationId[[:space:]]*=?[[:space:]]*\"[^\"]*\"/applicationId \"$BID_ANDROID\"/" "$GRADLE"
|
||||
rm -f "$GRADLE.bak"
|
||||
echo " ✓ applicationId → $BID_ANDROID"
|
||||
else
|
||||
echo " ⚠️ لم أجد build.gradle — تخطّي"
|
||||
fi
|
||||
|
||||
# ── 4. الأيقونات و splash من اللوغو ────────────────────────────────────
|
||||
echo " • توليد الأيقونات و splash من اللوغو…"
|
||||
curl -fsSL "$API/tenant/logo/$SLUG" -o "$WORK/assets/logo.png" 2>/dev/null || echo " (لا لوغو مرفوع — يُستعمل الافتراضي)"
|
||||
# [Q] flutter_launcher_icons + flutter_native_splash من assets/logo.png
|
||||
PBX="$WORK/ios/Runner.xcodeproj/project.pbxproj"
|
||||
if [ -n "$BID_IOS" ] && [ -f "$PBX" ]; then
|
||||
sed -i.bak -E "s/PRODUCT_BUNDLE_IDENTIFIER = [^;]*;/PRODUCT_BUNDLE_IDENTIFIER = $BID_IOS;/g" "$PBX"
|
||||
rm -f "$PBX.bak"
|
||||
echo " ✓ PRODUCT_BUNDLE_IDENTIFIER → $BID_IOS"
|
||||
fi
|
||||
|
||||
# ── 5. حقن FCM + إعداد المستأجر ────────────────────────────────────────
|
||||
echo " • حقن إعداد المستأجر ($COUNTRY)…"
|
||||
# [Q] نسخ google-services.json / GoogleService-Info.plist الخاص بالمستأجر.
|
||||
# BASE_URL و TENANT عبر --dart-define عند البناء.
|
||||
# ── 5. الأيقونات و splash من اللوغو ────────────────────────────────────
|
||||
echo " • الأيقونات و splash…"
|
||||
mkdir -p "$WORK/assets"
|
||||
if curl -fsSL "$API/tenant/logo/$SLUG" -o "$WORK/assets/logo.png" 2>/dev/null; then
|
||||
# الباك إند تحقّق من المقاس والتربيع عند الرفع (docs/22 — N2)، فما يصل هنا
|
||||
# صالحٌ لتوليد الأيقونات بلا تشويه.
|
||||
cat > "$WORK/flutter_launcher_icons.yaml" <<EOF
|
||||
flutter_launcher_icons:
|
||||
android: true
|
||||
ios: true
|
||||
image_path: "assets/logo.png"
|
||||
remove_alpha_ios: true
|
||||
EOF
|
||||
cat > "$WORK/flutter_native_splash.yaml" <<EOF
|
||||
flutter_native_splash:
|
||||
color: "#FFFFFF"
|
||||
image: assets/logo.png
|
||||
android_12:
|
||||
image: assets/logo.png
|
||||
EOF
|
||||
echo " ✓ لوغو المستأجر + إعدادات الأيقونات/splash"
|
||||
else
|
||||
echo " ⚠️ لا لوغو مرفوع — يُستعمل الافتراضي (ارفعه من لوحة السوبر-أدمن)"
|
||||
fi
|
||||
|
||||
# ── 6. البناء + Shorebird ──────────────────────────────────────────────
|
||||
echo " • البناء وتسجيل Shorebird…"
|
||||
# [Q] أول مرة: shorebird init (يسجّل app_id لهذا bundle ID في حساب Shorebird).
|
||||
# التحديثات: shorebird release / shorebird patch.
|
||||
# ( cd "$WORK" && shorebird release android \
|
||||
# --dart-define=BASE_URL=${API} --dart-define=TENANT=$SLUG )
|
||||
# ── 6. اسم التطبيق الظاهر ──────────────────────────────────────────────
|
||||
MANIFEST_XML="$WORK/android/app/src/main/AndroidManifest.xml"
|
||||
if [ -n "$APP_NAME" ] && [ -f "$MANIFEST_XML" ]; then
|
||||
sed -i.bak -E "s/android:label=\"[^\"]*\"/android:label=\"$APP_NAME\"/" "$MANIFEST_XML"
|
||||
rm -f "$MANIFEST_XML.bak"
|
||||
echo " • اسم التطبيق → $APP_NAME"
|
||||
fi
|
||||
|
||||
echo "✅ اكتمل توليد [$APP_KIND] لـ[$SLUG] (الخطوات الموسومة [Q] معلّقة حتى مشروع فلاتر)."
|
||||
# ── 7. البناء + Shorebird (اختياري) ────────────────────────────────────
|
||||
if [ "$DO_BUILD" != "--build" ]; then
|
||||
echo ""
|
||||
echo "✅ اكتمل التوليد في: $WORK"
|
||||
echo " للبناء أضف --build (يحتاج flutter + shorebird مثبّتين)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo " • الأيقونات والبناء…"
|
||||
cd "$WORK"
|
||||
flutter pub get
|
||||
dart run flutter_launcher_icons || echo " ⚠️ تخطّي الأيقونات"
|
||||
dart run flutter_native_splash:create || echo " ⚠️ تخطّي splash"
|
||||
|
||||
# ⚠️ الطبقة الأصيلة موحّدة عبر كل المستأجرين عمداً (docs/22 §1.5): بهذا تصير
|
||||
# ترقية الباقة patch عبر Shorebird بلا مراجعة متجر. لا تُفرِّع الأصيل هنا.
|
||||
shorebird release android \
|
||||
--dart-define=BASE_URL="$API" \
|
||||
--dart-define=TENANT="$SLUG"
|
||||
|
||||
echo "✅ اكتمل بناء [$APP_KIND] لـ[$SLUG]."
|
||||
|
||||
Reference in New Issue
Block a user