From 6fcaea72c179c3069b8b59211513b2597ba0f9e9 Mon Sep 17 00:00:00 2001
From: aimenng <141473804+aimenng@users.noreply.github.com>
Date: Thu, 12 Feb 2026 17:46:26 +0800
Subject: [PATCH 1/2] refactor(android): remove tracked binaries and bootstrap
wrapper at runtime
---
.env.android.example | 9 +
README.md | 134 ++-
android/.gitignore | 109 +++
android/app/.gitignore | 2 +
android/app/build.gradle | 127 +++
android/app/capacitor.build.gradle | 19 +
android/app/proguard-rules.pro | 21 +
.../myapp/ExampleInstrumentedTest.java | 26 +
android/app/src/main/AndroidManifest.xml | 41 +
.../gifts/coupleconnection/MainActivity.java | 5 +
.../drawable-v24/ic_launcher_foreground.xml | 34 +
.../res/drawable/ic_launcher_background.xml | 170 ++++
.../main/res/drawable/splash_background.xml | 7 +
.../app/src/main/res/layout/activity_main.xml | 12 +
.../res/mipmap-anydpi-v26/ic_launcher.xml | 5 +
.../mipmap-anydpi-v26/ic_launcher_round.xml | 5 +
android/app/src/main/res/values/colors.xml | 7 +
.../res/values/ic_launcher_background.xml | 4 +
android/app/src/main/res/values/strings.xml | 7 +
android/app/src/main/res/values/styles.xml | 28 +
android/app/src/main/res/xml/file_paths.xml | 5 +
.../getcapacitor/myapp/ExampleUnitTest.java | 18 +
android/build.gradle | 40 +
android/capacitor.settings.gradle | 3 +
android/gradle.properties | 31 +
.../gradle/wrapper/gradle-wrapper.properties | 7 +
android/gradlew | 252 +++++
android/gradlew.bat | 94 ++
android/keystore.properties.example | 6 +
android/settings.gradle | 5 +
android/variables.gradle | 16 +
capacitor.config.ts | 16 +
docs/ANDROID_RELEASE_GUIDE.md | 141 +++
package-lock.json | 888 +++++++++++++++++-
package.json | 13 +-
scripts/bootstrap-gradle-wrapper.sh | 15 +
scripts/create-android-keystore.sh | 55 ++
scripts/setup-android-sdk.sh | 31 +
utils/apiClient.ts | 12 +-
39 files changed, 2398 insertions(+), 22 deletions(-)
create mode 100644 .env.android.example
create mode 100644 android/.gitignore
create mode 100644 android/app/.gitignore
create mode 100644 android/app/build.gradle
create mode 100644 android/app/capacitor.build.gradle
create mode 100644 android/app/proguard-rules.pro
create mode 100644 android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java
create mode 100644 android/app/src/main/AndroidManifest.xml
create mode 100644 android/app/src/main/java/com/gifts/coupleconnection/MainActivity.java
create mode 100644 android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
create mode 100644 android/app/src/main/res/drawable/ic_launcher_background.xml
create mode 100644 android/app/src/main/res/drawable/splash_background.xml
create mode 100644 android/app/src/main/res/layout/activity_main.xml
create mode 100644 android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
create mode 100644 android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
create mode 100644 android/app/src/main/res/values/colors.xml
create mode 100644 android/app/src/main/res/values/ic_launcher_background.xml
create mode 100644 android/app/src/main/res/values/strings.xml
create mode 100644 android/app/src/main/res/values/styles.xml
create mode 100644 android/app/src/main/res/xml/file_paths.xml
create mode 100644 android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java
create mode 100644 android/build.gradle
create mode 100644 android/capacitor.settings.gradle
create mode 100644 android/gradle.properties
create mode 100644 android/gradle/wrapper/gradle-wrapper.properties
create mode 100755 android/gradlew
create mode 100644 android/gradlew.bat
create mode 100644 android/keystore.properties.example
create mode 100644 android/settings.gradle
create mode 100644 android/variables.gradle
create mode 100644 capacitor.config.ts
create mode 100644 docs/ANDROID_RELEASE_GUIDE.md
create mode 100755 scripts/bootstrap-gradle-wrapper.sh
create mode 100755 scripts/create-android-keystore.sh
create mode 100755 scripts/setup-android-sdk.sh
diff --git a/.env.android.example b/.env.android.example
new file mode 100644
index 0000000..92e6160
--- /dev/null
+++ b/.env.android.example
@@ -0,0 +1,9 @@
+# Android APP build env for frontend (Vite)
+# 必填:手机 APP 请求后端 API 的完整地址(建议 HTTPS)
+VITE_API_BASE_URL=https://your-backend-domain.com/api
+
+# 可选:仅当你在原生容器内运行并希望和 VITE_API_BASE_URL 区分时使用
+# VITE_API_BASE_URL_MOBILE=https://your-backend-domain.com/api
+
+# ⚠️ 不要在前端填写 SUPABASE_SERVICE_ROLE_KEY
+# Service Role Key 只能放在后端环境变量中
diff --git a/README.md b/README.md
index adef040..a6f009f 100644
--- a/README.md
+++ b/README.md
@@ -2,23 +2,133 @@
-# Run and deploy your AI Studio app
+# Couple Connection
-This contains everything you need to run your app locally.
+本仓库包含:
+- 前端(Vite + React)
+- 后端(Node + Express)
+- Supabase(数据库 + Storage)
+- Android 打包能力(Capacitor)
-View your app in AI Studio: https://ai.studio/apps/temp/1
+## 本地开发
-## AI Code Review
+**Prerequisites:** Node.js
-This repository has GPT-powered automated code review enabled. See [setup instructions](.github/GPT_CODE_REVIEW_SETUP.md) ([English](.github/GPT_CODE_REVIEW_SETUP_EN.md)) for configuration details.
+1. 安装依赖:
+ ```bash
+ npm install
+ ```
+2. 启动前端:
+ ```bash
+ npm run dev
+ ```
+3. 启动后端:
+ ```bash
+ npm run dev:backend
+ ```
+
+## Supabase 配置(重要)
+
+后端需要以下环境变量(放在后端运行环境,不要暴露到前端):
+
+- `SUPABASE_URL`
+- `SUPABASE_SERVICE_ROLE_KEY`
+
+> ⚠️ `SUPABASE_SERVICE_ROLE_KEY` 是高权限密钥,绝对不能写进前端 `.env` 或 APK。
+
+## 打包 Android APK(手机安装)
+
+> 说明:仓库已按“**不支持二进制文件**”要求移除图标/启动图 PNG 与 `gradle-wrapper.jar`。
+> - 图标与启动图改为 XML 资源;
+> - `gradle-wrapper.jar` 在执行 Android 打包命令时通过 `scripts/bootstrap-gradle-wrapper.sh` 自动下载。
+
+### 1) 先修复 Android SDK 环境
+
+```bash
+npm run android:sdk:setup
+```
+
+### 2) 配置移动端 API 地址
+
+复制并填写:
+
+```bash
+cp .env.android.example .env
+```
+
+至少配置:
+
+```bash
+VITE_API_BASE_URL=https://你的后端域名/api
+```
+
+### 3) 同步并生成 Android 工程
+
+```bash
+npm run build:mobile
+```
+
+### 4) 构建 Debug APK
+
+```bash
+npm run android:apk
+```
-## Run Locally
+APK 默认输出目录:
-**Prerequisites:** Node.js
+```text
+android/app/build/outputs/apk/official/debug/app-official-universal-debug.apk
+```
+### 5) 安装到手机(小米/华为等)
-1. Install dependencies:
- `npm install`
-2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
-3. Run the app:
- `npm run dev`
+```bash
+adb install -r android/app/build/outputs/apk/official/debug/app-official-universal-debug.apk
+```
+
+> 若是首次安装,需在手机上允许“安装未知应用”。
+
+## Release 签名与渠道包
+
+```bash
+npm run android:keystore:create -- android/app/release-keystore.jks coupleconnection 3650 "CN=YourApp, OU=Mobile, O=YourOrg, L=Shenzhen, ST=Guangdong, C=CN" JKS # 生成 release keystore 和 keystore.properties
+npm run android:apk:release # 输出 official 渠道 release APK
+npm run android:bundle:release # 输出 official 渠道 release AAB
+```
+
+## 多渠道 / 多 ABI
+
+已配置:
+- 渠道:`official` / `xiaomi` / `huawei`
+- ABI:`armeabi-v7a` / `arm64-v8a` / `x86_64`(含 universalApk)
+
+## 常用移动端命令
+
+```bash
+npm run android:sync # 仅同步 Web 资源和插件
+npm run android:open # 用 Android Studio 打开原生工程
+npm run android:apk # 构建 debug APK
+npm run android:apk:release # 构建 official 渠道 release APK
+```
+
+
+## 上架前最后一步(版本号/图标/启动图/包名渠道)
+
+请直接按该文档操作:
+
+- [docs/ANDROID_RELEASE_GUIDE.md](docs/ANDROID_RELEASE_GUIDE.md)
+
+文档已把以下内容整合为一套流程:
+- versionCode / versionName
+- 应用图标与启动图替换
+- 小米/华为/官方包名渠道策略
+
+## 真机兼容清单(小米/华为)
+
+请查看完整文档:
+
+- [docs/ANDROID_RELEASE_GUIDE.md](docs/ANDROID_RELEASE_GUIDE.md)
+
+## AI Code Review
+
+This repository has GPT-powered automated code review enabled. See [setup instructions](.github/GPT_CODE_REVIEW_SETUP.md) ([English](.github/GPT_CODE_REVIEW_SETUP_EN.md)) for configuration details.
diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 0000000..26a81b0
--- /dev/null
+++ b/android/.gitignore
@@ -0,0 +1,109 @@
+# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
+
+# Built application files
+*.apk
+*.aar
+*.ap_
+*.aab
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+# Uncomment the following line in case you need and you don't have the release build type files in your app
+# release/
+
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+*.iml
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/gradle.xml
+.idea/assetWizardSettings.xml
+.idea/dictionaries
+.idea/libraries
+# Android Studio 3 in .gitignore file.
+.idea/caches
+.idea/modules.xml
+# Comment next line if keeping position of elements in Navigation Editor is relevant for you
+.idea/navEditor.xml
+
+# Keystore files
+# Uncomment the following lines if you do not want to check your keystore files in.
+#*.jks
+#*.keystore
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+.cxx/
+
+# Google Services (e.g. APIs or Firebase)
+# google-services.json
+
+# Freeline
+freeline.py
+freeline/
+freeline_project_description.json
+
+# fastlane
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+fastlane/readme.md
+
+# Version control
+vcs.xml
+
+# lint
+lint/intermediates/
+lint/generated/
+lint/outputs/
+lint/tmp/
+# lint/reports/
+
+# Android Profiling
+*.hprof
+
+# Cordova plugins for Capacitor
+capacitor-cordova-android-plugins
+
+# Copied web assets
+app/src/main/assets/public
+
+# Generated Config files
+app/src/main/assets/capacitor.config.json
+app/src/main/assets/capacitor.plugins.json
+app/src/main/res/xml/config.xml
+
+# Release signing secrets
+keystore.properties
+app/*.jks
+app/*.keystore
+
+# Downloaded at runtime to keep repository text-only
+gradle/wrapper/gradle-wrapper.jar
diff --git a/android/app/.gitignore b/android/app/.gitignore
new file mode 100644
index 0000000..043df80
--- /dev/null
+++ b/android/app/.gitignore
@@ -0,0 +1,2 @@
+/build/*
+!/build/.npmkeep
diff --git a/android/app/build.gradle b/android/app/build.gradle
new file mode 100644
index 0000000..cdd2a80
--- /dev/null
+++ b/android/app/build.gradle
@@ -0,0 +1,127 @@
+apply plugin: 'com.android.application'
+
+import java.util.Properties
+
+final Properties keystoreProperties = new Properties()
+final File keystorePropertiesFile = rootProject.file('keystore.properties')
+if (keystorePropertiesFile.exists()) {
+ keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
+}
+
+final int appVersionCode = (project.findProperty('APP_VERSION_CODE') ?: '1') as Integer
+final String appVersionName = String.valueOf(project.findProperty('APP_VERSION_NAME') ?: '1.0.0')
+final String appIdBase = String.valueOf(project.findProperty('APP_ID_BASE') ?: 'com.gifts.coupleconnection')
+final String appIdXiaomi = String.valueOf(project.findProperty('APP_ID_XIAOMI') ?: appIdBase + '.mi')
+final String appIdHuawei = String.valueOf(project.findProperty('APP_ID_HUAWEI') ?: appIdBase + '.hw')
+
+android {
+ namespace "com.gifts.coupleconnection"
+ compileSdk rootProject.ext.compileSdkVersion
+
+ defaultConfig {
+ applicationId appIdBase
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ versionCode appVersionCode
+ versionName appVersionName
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ ndk {
+ abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64'
+ }
+
+ aaptOptions {
+ // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
+ // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
+ ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
+ }
+ }
+
+ signingConfigs {
+ release {
+ if (keystorePropertiesFile.exists()) {
+ keyAlias keystoreProperties['keyAlias']
+ keyPassword keystoreProperties['keyPassword']
+ storeFile file(keystoreProperties['storeFile'])
+ storePassword keystoreProperties['storePassword']
+ }
+ }
+ }
+
+ buildTypes {
+ debug {
+ debuggable true
+ }
+ release {
+ minifyEnabled false
+ shrinkResources false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ if (keystorePropertiesFile.exists()) {
+ signingConfig signingConfigs.release
+ }
+ }
+ }
+
+ flavorDimensions += ["channel"]
+ productFlavors {
+ official {
+ dimension "channel"
+ applicationId appIdBase
+ resValue 'string', 'app_name', 'Couple Connection'
+ manifestPlaceholders = [channelName: 'official']
+ }
+ xiaomi {
+ dimension "channel"
+ applicationId appIdXiaomi
+ resValue 'string', 'app_name', 'Couple Connection 小米版'
+ versionNameSuffix '-xm'
+ manifestPlaceholders = [channelName: 'xiaomi']
+ }
+ huawei {
+ dimension "channel"
+ applicationId appIdHuawei
+ resValue 'string', 'app_name', 'Couple Connection 华为版'
+ versionNameSuffix '-hw'
+ manifestPlaceholders = [channelName: 'huawei']
+ }
+ }
+
+ splits {
+ abi {
+ enable true
+ reset()
+ include 'armeabi-v7a', 'arm64-v8a', 'x86_64'
+ universalApk true
+ }
+ }
+
+}
+
+repositories {
+ flatDir {
+ dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
+ }
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
+ implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
+ implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
+ implementation project(':capacitor-android')
+ testImplementation "junit:junit:$junitVersion"
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
+ implementation project(':capacitor-cordova-android-plugins')
+}
+
+apply from: 'capacitor.build.gradle'
+
+try {
+ def servicesJSON = file('google-services.json')
+ if (servicesJSON.text) {
+ apply plugin: 'com.google.gms.google-services'
+ }
+} catch (Exception e) {
+ logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
+}
diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle
new file mode 100644
index 0000000..bbfb44f
--- /dev/null
+++ b/android/app/capacitor.build.gradle
@@ -0,0 +1,19 @@
+// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
+
+android {
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_21
+ targetCompatibility JavaVersion.VERSION_21
+ }
+}
+
+apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
+dependencies {
+
+
+}
+
+
+if (hasProperty('postBuildExtras')) {
+ postBuildExtras()
+}
diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/android/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java b/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..f2c2217
--- /dev/null
+++ b/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.getcapacitor.myapp;
+
+import static org.junit.Assert.*;
+
+import android.content.Context;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ assertEquals("com.getcapacitor.app", appContext.getPackageName());
+ }
+}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..340e7df
--- /dev/null
+++ b/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/java/com/gifts/coupleconnection/MainActivity.java b/android/app/src/main/java/com/gifts/coupleconnection/MainActivity.java
new file mode 100644
index 0000000..e7e073a
--- /dev/null
+++ b/android/app/src/main/java/com/gifts/coupleconnection/MainActivity.java
@@ -0,0 +1,5 @@
+package com.gifts.coupleconnection;
+
+import com.getcapacitor.BridgeActivity;
+
+public class MainActivity extends BridgeActivity {}
diff --git a/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..c7bd21d
--- /dev/null
+++ b/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_launcher_background.xml b/android/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..d5fccc5
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/splash_background.xml b/android/app/src/main/res/drawable/splash_background.xml
new file mode 100644
index 0000000..413daf3
--- /dev/null
+++ b/android/app/src/main/res/drawable/splash_background.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..b5ad138
--- /dev/null
+++ b/android/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..7353dbd
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..7353dbd
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..b1142ea
--- /dev/null
+++ b/android/app/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #8A9A5B
+ #728961
+ #F43F5E
+ #1D2210
+
diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..c5d5899
--- /dev/null
+++ b/android/app/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..7c9678e
--- /dev/null
+++ b/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+
+
+ Couple Connection
+ Couple Connection
+ com.gifts.coupleconnection
+ com.gifts.coupleconnection
+
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..ccef46b
--- /dev/null
+++ b/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/xml/file_paths.xml b/android/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 0000000..bd0c4d8
--- /dev/null
+++ b/android/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java b/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java
new file mode 100644
index 0000000..0297327
--- /dev/null
+++ b/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java
@@ -0,0 +1,18 @@
+package com.getcapacitor.myapp;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+}
diff --git a/android/build.gradle b/android/build.gradle
new file mode 100644
index 0000000..5bdfeea
--- /dev/null
+++ b/android/build.gradle
@@ -0,0 +1,40 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:8.7.2'
+ classpath 'com.google.gms:google-services:4.4.2'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+apply from: "variables.gradle"
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+
+subprojects {
+ configurations.all {
+ resolutionStrategy {
+ force 'org.jetbrains.kotlin:kotlin-stdlib:1.8.22'
+ }
+ exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
+ exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle
new file mode 100644
index 0000000..9a5fa87
--- /dev/null
+++ b/android/capacitor.settings.gradle
@@ -0,0 +1,3 @@
+// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
+include ':capacitor-android'
+project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
diff --git a/android/gradle.properties b/android/gradle.properties
new file mode 100644
index 0000000..48839a6
--- /dev/null
+++ b/android/gradle.properties
@@ -0,0 +1,31 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+org.gradle.jvmargs=-Xmx1536m
+
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+
+# ---------- Release version strategy ----------
+# 修改这里即可统一管理所有渠道版本号
+APP_VERSION_CODE=10001
+APP_VERSION_NAME=1.0.1
+
+# ---------- Package/channel strategy ----------
+# 官方包(Google Play / 通用发布)
+APP_ID_BASE=com.gifts.coupleconnection
+
+# 小米渠道包(建议独立包名,便于渠道独立审核与灰度)
+APP_ID_XIAOMI=com.gifts.coupleconnection.mi
+
+# 华为渠道包(建议独立包名,便于华为市场独立版本节奏)
+APP_ID_HUAWEI=com.gifts.coupleconnection.hw
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..c1d5e01
--- /dev/null
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/android/gradlew b/android/gradlew
new file mode 100755
index 0000000..f5feea6
--- /dev/null
+++ b/android/gradlew
@@ -0,0 +1,252 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
+' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/android/gradlew.bat b/android/gradlew.bat
new file mode 100644
index 0000000..9b42019
--- /dev/null
+++ b/android/gradlew.bat
@@ -0,0 +1,94 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/android/keystore.properties.example b/android/keystore.properties.example
new file mode 100644
index 0000000..4c0bed6
--- /dev/null
+++ b/android/keystore.properties.example
@@ -0,0 +1,6 @@
+# Copy to android/keystore.properties and fill real values.
+# Do NOT commit android/keystore.properties or your .jks file.
+storeFile=release-keystore.jks
+storePassword=change-me
+keyAlias=coupleconnection
+keyPassword=change-me
diff --git a/android/settings.gradle b/android/settings.gradle
new file mode 100644
index 0000000..3b4431d
--- /dev/null
+++ b/android/settings.gradle
@@ -0,0 +1,5 @@
+include ':app'
+include ':capacitor-cordova-android-plugins'
+project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
+
+apply from: 'capacitor.settings.gradle'
\ No newline at end of file
diff --git a/android/variables.gradle b/android/variables.gradle
new file mode 100644
index 0000000..2c8e408
--- /dev/null
+++ b/android/variables.gradle
@@ -0,0 +1,16 @@
+ext {
+ minSdkVersion = 23
+ compileSdkVersion = 35
+ targetSdkVersion = 35
+ androidxActivityVersion = '1.9.2'
+ androidxAppCompatVersion = '1.7.0'
+ androidxCoordinatorLayoutVersion = '1.2.0'
+ androidxCoreVersion = '1.15.0'
+ androidxFragmentVersion = '1.8.4'
+ coreSplashScreenVersion = '1.0.1'
+ androidxWebkitVersion = '1.12.1'
+ junitVersion = '4.13.2'
+ androidxJunitVersion = '1.2.1'
+ androidxEspressoCoreVersion = '3.6.1'
+ cordovaAndroidVersion = '10.1.1'
+}
\ No newline at end of file
diff --git a/capacitor.config.ts b/capacitor.config.ts
new file mode 100644
index 0000000..b7845f2
--- /dev/null
+++ b/capacitor.config.ts
@@ -0,0 +1,16 @@
+import type { CapacitorConfig } from '@capacitor/cli';
+
+const config: CapacitorConfig = {
+ appId: 'com.gifts.coupleconnection',
+ appName: 'Couple Connection',
+ webDir: 'dist',
+ android: {
+ allowMixedContent: true,
+ captureInput: true,
+ },
+ server: {
+ androidScheme: 'https',
+ },
+};
+
+export default config;
diff --git a/docs/ANDROID_RELEASE_GUIDE.md b/docs/ANDROID_RELEASE_GUIDE.md
new file mode 100644
index 0000000..96db15f
--- /dev/null
+++ b/docs/ANDROID_RELEASE_GUIDE.md
@@ -0,0 +1,141 @@
+# Android 上架前最后一步(版本号 / 图标 / 启动图 / 包名渠道)
+
+## 0) 你现在要改的 4 个核心位置
+
+> 兼容约束:仓库按“**不支持二进制文件**”维护,图标/启动图使用 XML 资源,`gradle-wrapper.jar` 通过脚本自动下载。
+
+1. **版本号**:`android/gradle.properties`
+2. **包名(渠道策略)**:`android/gradle.properties` + `android/app/build.gradle`
+3. **应用图标**:`android/app/src/main/res/mipmap-*` + `mipmap-anydpi-v26`
+4. **启动图(Splash)**:`android/app/src/main/res/values/styles.xml` + `values/colors.xml` + `drawable/splash_background.xml`
+
+---
+
+## 1) versionCode / versionName(统一管理)
+
+在 `android/gradle.properties` 修改:
+
+```properties
+APP_VERSION_CODE=10001
+APP_VERSION_NAME=1.0.1
+```
+
+规则建议:
+- 每次提审 `APP_VERSION_CODE` 必须递增。
+- `APP_VERSION_NAME` 用语义版本(如 `1.0.1` / `1.1.0`)。
+
+`android/app/build.gradle` 已自动读取这两个值,无需多处重复改。
+
+---
+
+## 2) 包名渠道策略(小米 / 华为 / 官方)
+
+在 `android/gradle.properties` 统一配置:
+
+```properties
+APP_ID_BASE=com.gifts.coupleconnection
+APP_ID_XIAOMI=com.gifts.coupleconnection.mi
+APP_ID_HUAWEI=com.gifts.coupleconnection.hw
+```
+
+`android/app/build.gradle` 已配置 3 个 flavor:
+- `official`:官方包(通用)
+- `xiaomi`:小米市场包(独立包名)
+- `huawei`:华为市场包(独立包名)
+
+这样做的好处:
+- 各渠道独立审核与发布节奏。
+- 渠道热修复互不影响。
+- 便于灰度和分渠道统计。
+
+---
+
+## 3) 应用图标(Launcher Icon)
+
+### 当前工程已就绪
+- 已包含 adaptive icon 配置(`mipmap-anydpi-v26`)。
+- 已有默认图标资源,可直接构建。
+
+### 上架前建议替换
+- 使用 Android Studio:`New -> Image Asset`
+- 生成并覆盖:
+ - `mipmap-mdpi/ic_launcher.png`
+ - `mipmap-hdpi/ic_launcher.png`
+ - `mipmap-xhdpi/ic_launcher.png`
+ - `mipmap-xxhdpi/ic_launcher.png`
+ - `mipmap-xxxhdpi/ic_launcher.png`
+ - 对应 `ic_launcher_round` 与 `ic_launcher_foreground`
+
+建议导出规格:1024x1024 源图(PNG,透明背景)。
+
+---
+
+## 4) 启动图(Splash)
+
+当前已整理为可配置结构:
+- 背景色:`@color/splashBackground`(`values/colors.xml`)
+- 启动主题:`AppTheme.NoActionBarLaunch`(`values/styles.xml`)
+- 背景层:`drawable/splash_background.xml`
+
+你可按品牌更新:
+1. `colors.xml` 改 `splashBackground`
+2. `drawable/splash.png` 换你的启动图
+3. 若需纯 Logo 启动,可替换 `windowSplashScreenAnimatedIcon`
+
+---
+
+## 5) 构建命令(不强制现在生成 APK)
+
+```bash
+npm run android:sdk:setup
+npm run build:mobile
+npm run android:apk:release
+npm run android:bundle:release
+```
+
+### keystore 自动配置与验证(已可直接执行)
+
+你可以直接执行:
+
+```bash
+bash scripts/create-android-keystore.sh /tmp/test-release.jks testalias testStorePass123 testKeyPass123 30 "CN=Test, OU=QA, O=QA, L=SZ, ST=GD, C=CN" JKS
+```
+
+脚本会自动生成/更新:
+- `android/keystore.properties`
+- `storeFile` 将自动转换为相对 `android/` 的路径,Gradle 可直接识别。
+
+渠道包(可选):
+
+```bash
+cd android
+./gradlew assembleXiaomiRelease
+./gradlew assembleHuaweiRelease
+```
+
+---
+
+## 6) 兼容性清单(提审前)
+
+### 通用
+- [ ] Android 10~14 真机覆盖。
+- [ ] 首启权限文案与隐私协议齐全。
+- [ ] 弱网/断网/后台恢复流程正常。
+
+### 小米
+- [ ] 自启动允许。
+- [ ] 电池策略设为“无限制”(如有保活需求)。
+- [ ] 通知权限、锁屏通知、悬浮通知开启指引。
+
+### 华为
+- [ ] 启动管理设置为“手动管理”。
+- [ ] 电池优化加入白名单(如有保活需求)。
+- [ ] 若使用华为推送/账号能力,补 HMS 合规材料。
+
+---
+
+## 7) 安全提醒(非常重要)
+
+- `SUPABASE_SERVICE_ROLE_KEY` 只允许后端保存。
+- 前端与 APK 仅可放公开配置(如 API Base URL)。
+- 若密钥曾泄露,请立即在 Supabase 控制台轮换。
diff --git a/package-lock.json b/package-lock.json
index 3955dcf..5e17a6f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,9 @@
"name": "gifts---couple-connection",
"version": "0.0.0",
"dependencies": {
+ "@capacitor/android": "^8.1.0",
+ "@capacitor/cli": "^7.5.0",
+ "@capacitor/core": "^8.1.0",
"@supabase/supabase-js": "^2.95.3",
"bcryptjs": "^3.0.3",
"compression": "^1.8.1",
@@ -63,7 +66,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -315,6 +317,68 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@capacitor/android": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@capacitor/android/-/android-8.1.0.tgz",
+ "integrity": "sha512-z0acTPxj5DCy/U2FU7w+GA93oC+wdyKnsOcRg5rutDmSYa8Do1tzYqApKgf+hnuTNPbtrCTHp0Zy1cLiK/4MEw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@capacitor/core": "^8.1.0"
+ }
+ },
+ "node_modules/@capacitor/cli": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-7.5.0.tgz",
+ "integrity": "sha512-mlohsvLZjWrO5eAVTn1+dABNQwQawcphVp6NQVJZ3I4x2BAoNmJj53QflX7PYGUipL9gF9EM9Yiku3m1McxFZg==",
+ "license": "MIT",
+ "dependencies": {
+ "@ionic/cli-framework-output": "^2.2.8",
+ "@ionic/utils-subprocess": "^3.0.1",
+ "@ionic/utils-terminal": "^2.3.5",
+ "commander": "^12.1.0",
+ "debug": "^4.4.0",
+ "env-paths": "^2.2.0",
+ "fs-extra": "^11.2.0",
+ "kleur": "^4.1.5",
+ "native-run": "^2.0.3",
+ "open": "^8.4.0",
+ "plist": "^3.1.0",
+ "prompts": "^2.4.2",
+ "rimraf": "^6.0.1",
+ "semver": "^7.6.3",
+ "tar": "^7.5.3",
+ "tslib": "^2.8.1",
+ "xml2js": "^0.6.2"
+ },
+ "bin": {
+ "cap": "bin/capacitor",
+ "capacitor": "bin/capacitor"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@capacitor/cli/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@capacitor/core": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.1.0.tgz",
+ "integrity": "sha512-UfMBMWc1v7J+14AhH03QmeNwV3HZx3qnOWhpwnHfzALEwAwlV/itQOQqcasMQYhOHWL0tiymc5ByaLTn7KKQxw==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
@@ -757,6 +821,178 @@
"node": ">=18"
}
},
+ "node_modules/@ionic/cli-framework-output": {
+ "version": "2.2.8",
+ "resolved": "https://registry.npmjs.org/@ionic/cli-framework-output/-/cli-framework-output-2.2.8.tgz",
+ "integrity": "sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==",
+ "license": "MIT",
+ "dependencies": {
+ "@ionic/utils-terminal": "2.3.5",
+ "debug": "^4.0.0",
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/@ionic/utils-array": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@ionic/utils-array/-/utils-array-2.1.6.tgz",
+ "integrity": "sha512-0JZ1Zkp3wURnv8oq6Qt7fMPo5MpjbLoUoa9Bu2Q4PJuSDWM8H8gwF3dQO7VTeUj3/0o1IB1wGkFWZZYgUXZMUg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.0.0",
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/@ionic/utils-fs": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@ionic/utils-fs/-/utils-fs-3.1.7.tgz",
+ "integrity": "sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/fs-extra": "^8.0.0",
+ "debug": "^4.0.0",
+ "fs-extra": "^9.0.0",
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/@ionic/utils-fs/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "license": "MIT",
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@ionic/utils-object": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@ionic/utils-object/-/utils-object-2.1.6.tgz",
+ "integrity": "sha512-vCl7sl6JjBHFw99CuAqHljYJpcE88YaH2ZW4ELiC/Zwxl5tiwn4kbdP/gxi2OT3MQb1vOtgAmSNRtusvgxI8ww==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.0.0",
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/@ionic/utils-process": {
+ "version": "2.1.12",
+ "resolved": "https://registry.npmjs.org/@ionic/utils-process/-/utils-process-2.1.12.tgz",
+ "integrity": "sha512-Jqkgyq7zBs/v/J3YvKtQQiIcxfJyplPgECMWgdO0E1fKrrH8EF0QGHNJ9mJCn6PYe2UtHNS8JJf5G21e09DfYg==",
+ "license": "MIT",
+ "dependencies": {
+ "@ionic/utils-object": "2.1.6",
+ "@ionic/utils-terminal": "2.3.5",
+ "debug": "^4.0.0",
+ "signal-exit": "^3.0.3",
+ "tree-kill": "^1.2.2",
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/@ionic/utils-stream": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@ionic/utils-stream/-/utils-stream-3.1.7.tgz",
+ "integrity": "sha512-eSELBE7NWNFIHTbTC2jiMvh1ABKGIpGdUIvARsNPMNQhxJB3wpwdiVnoBoTYp+5a6UUIww4Kpg7v6S7iTctH1w==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.0.0",
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/@ionic/utils-subprocess": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@ionic/utils-subprocess/-/utils-subprocess-3.0.1.tgz",
+ "integrity": "sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A==",
+ "license": "MIT",
+ "dependencies": {
+ "@ionic/utils-array": "2.1.6",
+ "@ionic/utils-fs": "3.1.7",
+ "@ionic/utils-process": "2.1.12",
+ "@ionic/utils-stream": "3.1.7",
+ "@ionic/utils-terminal": "2.3.5",
+ "cross-spawn": "^7.0.3",
+ "debug": "^4.0.0",
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/@ionic/utils-terminal": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@ionic/utils-terminal/-/utils-terminal-2.3.5.tgz",
+ "integrity": "sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/slice-ansi": "^4.0.0",
+ "debug": "^4.0.0",
+ "signal-exit": "^3.0.3",
+ "slice-ansi": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0",
+ "tslib": "^2.0.1",
+ "untildify": "^4.0.0",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/@isaacs/balanced-match": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
+ "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@isaacs/brace-expansion": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz",
+ "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@isaacs/balanced-match": "^4.0.1"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@isaacs/fs-minipass": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
+ "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.4"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -1296,6 +1532,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/fs-extra": {
+ "version": "8.1.5",
+ "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.5.tgz",
+ "integrity": "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/node": {
"version": "22.19.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.8.tgz",
@@ -1317,7 +1562,6 @@
"integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -1332,6 +1576,12 @@
"@types/react": "^19.2.0"
}
},
+ "node_modules/@types/slice-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@types/slice-ansi/-/slice-ansi-4.0.0.tgz",
+ "integrity": "sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==",
+ "license": "MIT"
+ },
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz",
@@ -1362,6 +1612,15 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
+ "node_modules/@xmldom/xmldom": {
+ "version": "0.8.11",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
+ "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz",
@@ -1399,6 +1658,44 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/astral-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/at-least-node": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
+ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/baseline-browser-mapping": {
"version": "2.9.19",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
@@ -1418,6 +1715,15 @@
"bcrypt": "bin/bcrypt"
}
},
+ "node_modules/big-integer": {
+ "version": "1.6.52",
+ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
+ "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
+ "license": "Unlicense",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
"node_modules/body-parser": {
"version": "2.2.2",
"resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-2.2.2.tgz",
@@ -1442,6 +1748,18 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/bplist-parser": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.2.tgz",
+ "integrity": "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==",
+ "license": "MIT",
+ "dependencies": {
+ "big-integer": "1.6.x"
+ },
+ "engines": {
+ "node": ">= 5.10.0"
+ }
+ },
"node_modules/browserslist": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
@@ -1462,7 +1780,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -1477,6 +1794,15 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
@@ -1570,6 +1896,15 @@
"node": ">=8"
}
},
+ "node_modules/chownr": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
+ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz",
@@ -1602,6 +1937,15 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
+ "node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/compressible": {
"version": "2.0.18",
"resolved": "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz",
@@ -1744,6 +2088,20 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -1774,6 +2132,15 @@
}
}
},
+ "node_modules/define-lazy-prop": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
+ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz",
@@ -1831,6 +2198,18 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/elementtree": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/elementtree/-/elementtree-0.1.7.tgz",
+ "integrity": "sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "sax": "1.1.4"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -1846,6 +2225,15 @@
"node": ">= 0.8"
}
},
+ "node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -1947,7 +2335,6 @@
"resolved": "https://registry.npmmirror.com/express/-/express-5.2.1.tgz",
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.1",
@@ -2001,6 +2388,15 @@
"express": ">= 4.11"
}
},
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+ "license": "MIT",
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -2058,6 +2454,20 @@
"node": ">= 0.8"
}
},
+ "node_modules/fs-extra": {
+ "version": "11.3.3",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz",
+ "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -2138,6 +2548,23 @@
"node": ">= 0.4"
}
},
+ "node_modules/glob": {
+ "version": "13.0.2",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.2.tgz",
+ "integrity": "sha512-035InabNu/c1lW0tzPhAgapKctblppqsKKG9ZaNzbr+gXwWMjXoiyGSyB9sArzrjG7jY+zntRq5ZSUYemrnWVQ==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "minimatch": "^10.1.2",
+ "minipass": "^7.1.2",
+ "path-scurry": "^2.0.0"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
@@ -2150,6 +2577,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
@@ -2234,6 +2667,15 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
+ "node_modules/ini": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz",
+ "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==",
+ "license": "ISC",
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@@ -2243,6 +2685,21 @@
"node": ">= 0.10"
}
},
+ "node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -2258,6 +2715,24 @@
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
+ "node_modules/is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -2291,6 +2766,18 @@
"node": ">=6"
}
},
+ "node_modules/jsonfile": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
+ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
"node_modules/jsonwebtoken": {
"version": "9.0.3",
"resolved": "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
@@ -2346,6 +2833,15 @@
"safe-buffer": "^5.0.1"
}
},
+ "node_modules/kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz",
@@ -2462,6 +2958,42 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/minimatch": {
+ "version": "10.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz",
+ "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/brace-expansion": "^5.0.1"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
+ "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -2487,6 +3019,31 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/native-run": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/native-run/-/native-run-2.0.3.tgz",
+ "integrity": "sha512-U1PllBuzW5d1gfan+88L+Hky2eZx+9gv3Pf6rNBxKbORxi7boHzqiA6QFGSnqMem4j0A9tZ08NMIs5+0m/VS1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@ionic/utils-fs": "^3.1.7",
+ "@ionic/utils-terminal": "^2.3.4",
+ "bplist-parser": "^0.3.2",
+ "debug": "^4.3.4",
+ "elementtree": "^0.1.7",
+ "ini": "^4.1.1",
+ "plist": "^3.1.0",
+ "split2": "^4.2.0",
+ "through2": "^4.0.2",
+ "tslib": "^2.6.2",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "native-run": "bin/native-run"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-1.0.0.tgz",
@@ -2563,6 +3120,29 @@
"wrappy": "1"
}
},
+ "node_modules/open": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
+ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
+ "license": "MIT",
+ "dependencies": {
+ "define-lazy-prop": "^2.0.0",
+ "is-docker": "^2.1.1",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "license": "BlueOak-1.0.0"
+ },
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz",
@@ -2572,6 +3152,40 @@
"node": ">= 0.8"
}
},
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-scurry": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz",
+ "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^11.0.0",
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "11.2.6",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
+ "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
"node_modules/path-to-regexp": {
"version": "8.3.0",
"resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
@@ -2582,6 +3196,12 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "license": "MIT"
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -2595,7 +3215,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -2603,6 +3222,20 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/plist": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
+ "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@xmldom/xmldom": "^0.8.8",
+ "base64-js": "^1.5.1",
+ "xmlbuilder": "^15.1.1"
+ },
+ "engines": {
+ "node": ">=10.4.0"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
@@ -2632,6 +3265,28 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/prompts": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
+ "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "kleur": "^3.0.3",
+ "sisteransi": "^1.0.5"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/prompts/node_modules/kleur": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -2689,7 +3344,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -2716,6 +3370,20 @@
"node": ">=0.10.0"
}
},
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
@@ -2725,6 +3393,25 @@
"node": ">=0.10.0"
}
},
+ "node_modules/rimraf": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.2.tgz",
+ "integrity": "sha512-cFCkPslJv7BAXJsYlK1dZsbP8/ZNLkCAQ0bi1hf5EKX2QHegmDFEFA6QhuYJlk7UDdc+02JjO80YSOrWPpw06g==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "glob": "^13.0.0",
+ "package-json-from-dist": "^1.0.1"
+ },
+ "bin": {
+ "rimraf": "dist/esm/bin.mjs"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/rollup": {
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
@@ -2821,6 +3508,12 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
+ "node_modules/sax": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.4.tgz",
+ "integrity": "sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==",
+ "license": "ISC"
+ },
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
@@ -2888,6 +3581,27 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/shell-quote": {
"version": "1.8.3",
"resolved": "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.3.tgz",
@@ -2972,6 +3686,35 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "license": "ISC"
+ },
+ "node_modules/sisteransi": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
+ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
+ "license": "MIT"
+ },
+ "node_modules/slice-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
+ "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -2982,6 +3725,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz",
@@ -2991,6 +3743,15 @@
"node": ">= 0.8"
}
},
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
@@ -3032,6 +3793,40 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
+ "node_modules/tar": {
+ "version": "7.5.7",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
+ "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/fs-minipass": "^4.0.0",
+ "chownr": "^3.0.0",
+ "minipass": "^7.1.2",
+ "minizlib": "^3.1.0",
+ "yallist": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tar/node_modules/yallist": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
+ "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/through2": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz",
+ "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==",
+ "license": "MIT",
+ "dependencies": {
+ "readable-stream": "3"
+ }
+ },
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -3107,6 +3902,15 @@
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
},
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz",
@@ -3116,6 +3920,15 @@
"node": ">= 0.8"
}
},
+ "node_modules/untildify": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
+ "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
@@ -3147,6 +3960,12 @@
"browserslist": ">= 4.21.0"
}
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz",
@@ -3162,7 +3981,6 @@
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -3232,6 +4050,21 @@
}
}
},
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -3276,6 +4109,37 @@
}
}
},
+ "node_modules/xml2js": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
+ "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
+ "license": "MIT",
+ "dependencies": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/xml2js/node_modules/xmlbuilder": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/xmlbuilder": {
+ "version": "15.1.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
+ "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz",
@@ -3318,6 +4182,16 @@
"engines": {
"node": ">=12"
}
+ },
+ "node_modules/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
}
}
}
diff --git a/package.json b/package.json
index bb4e338..19386de 100644
--- a/package.json
+++ b/package.json
@@ -12,9 +12,20 @@
"start:backend": "node backend/src/index.js",
"build": "vite build",
"preview": "vite preview",
- "check:text-encoding": "node scripts/check-text-encoding.mjs"
+ "check:text-encoding": "node scripts/check-text-encoding.mjs",
+ "build:mobile": "npm run build && npx cap sync android",
+ "android:sync": "npx cap sync android",
+ "android:open": "npx cap open android",
+ "android:apk": "npm run build:mobile && bash scripts/bootstrap-gradle-wrapper.sh && cd android && ./gradlew assembleDebug",
+ "android:sdk:setup": "bash scripts/setup-android-sdk.sh",
+ "android:keystore:create": "bash scripts/create-android-keystore.sh",
+ "android:apk:release": "npm run build:mobile && bash scripts/bootstrap-gradle-wrapper.sh && cd android && ./gradlew assembleOfficialRelease",
+ "android:bundle:release": "npm run build:mobile && bash scripts/bootstrap-gradle-wrapper.sh && cd android && ./gradlew bundleOfficialRelease"
},
"dependencies": {
+ "@capacitor/android": "^8.1.0",
+ "@capacitor/cli": "^7.5.0",
+ "@capacitor/core": "^8.1.0",
"@supabase/supabase-js": "^2.95.3",
"bcryptjs": "^3.0.3",
"compression": "^1.8.1",
diff --git a/scripts/bootstrap-gradle-wrapper.sh b/scripts/bootstrap-gradle-wrapper.sh
new file mode 100755
index 0000000..f582ce5
--- /dev/null
+++ b/scripts/bootstrap-gradle-wrapper.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+WRAPPER_JAR="android/gradle/wrapper/gradle-wrapper.jar"
+WRAPPER_URL="https://raw.githubusercontent.com/gradle/gradle/v8.11.1/gradle/wrapper/gradle-wrapper.jar"
+
+if [ -f "$WRAPPER_JAR" ]; then
+ echo "[Gradle Wrapper] gradle-wrapper.jar already exists."
+ exit 0
+fi
+
+mkdir -p "$(dirname "$WRAPPER_JAR")"
+echo "[Gradle Wrapper] downloading gradle-wrapper.jar..."
+curl -fsSL "$WRAPPER_URL" -o "$WRAPPER_JAR"
+echo "[Gradle Wrapper] downloaded to $WRAPPER_JAR"
diff --git a/scripts/create-android-keystore.sh b/scripts/create-android-keystore.sh
new file mode 100755
index 0000000..bb634d9
--- /dev/null
+++ b/scripts/create-android-keystore.sh
@@ -0,0 +1,55 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+KEYSTORE_PATH="${1:-android/app/release-keystore.jks}"
+ALIAS="${2:-coupleconnection}"
+STOREPASS="${3:-}"
+KEYPASS="${4:-}"
+VALIDITY_DAYS="${5:-3650}"
+DNAME="${6:-CN=Couple Connection, OU=Mobile, O=Gifts, L=Shenzhen, ST=Guangdong, C=CN}"
+STORE_TYPE="${7:-JKS}"
+
+if [ -z "$STOREPASS" ]; then
+ STOREPASS="$(openssl rand -base64 24 | tr -d '=+/\n' | cut -c1-24)"
+ echo "[Keystore] storePassword not provided, generated a random one."
+fi
+if [ -z "$KEYPASS" ]; then
+ KEYPASS="$(openssl rand -base64 24 | tr -d '=+/\n' | cut -c1-24)"
+ echo "[Keystore] keyPassword not provided, generated a random one."
+fi
+
+mkdir -p "$(dirname "$KEYSTORE_PATH")"
+
+keytool -genkeypair \
+ -v \
+ -storetype "$STORE_TYPE" \
+ -keystore "$KEYSTORE_PATH" \
+ -alias "$ALIAS" \
+ -keyalg RSA \
+ -keysize 2048 \
+ -validity "$VALIDITY_DAYS" \
+ -storepass "$STOREPASS" \
+ -keypass "$KEYPASS" \
+ -dname "$DNAME"
+
+# storeFile is relative to android/ (rootProject.file resolution)
+REL_STORE_FILE=$(KEYSTORE_PATH="$KEYSTORE_PATH" python - <<'PY'
+import os
+from pathlib import Path
+keystore = Path(os.environ['KEYSTORE_PATH']).resolve()
+android_dir = Path('android').resolve()
+print(os.path.relpath(keystore, android_dir))
+PY
+)
+
+cat > android/keystore.properties </dev/null || true
+sdkmanager "platform-tools" "platforms;android-35" "build-tools;35.0.0"
+
+cat > android/local.properties <(path: string, body?: unknown): Promise =>
export const apiPatch = (path: string, body: unknown): Promise =>
apiRequest(path, { method: 'PATCH', body: JSON.stringify(body) });
-export const apiDelete = (path: string): Promise => apiRequest(path, { method: 'DELETE' });
\ No newline at end of file
+export const apiDelete = (path: string): Promise => apiRequest(path, { method: 'DELETE' });
From da926c3520247949a297eb286a642ff277a83035 Mon Sep 17 00:00:00 2001
From: aimenng <141473804+aimenng@users.noreply.github.com>
Date: Thu, 12 Feb 2026 18:20:09 +0800
Subject: [PATCH 2/2] Fix Android Capacitor WebView binding and cleartext
config
---
android/app/src/main/AndroidManifest.xml | 1 +
android/app/src/main/res/layout/activity_main.xml | 1 +
2 files changed, 2 insertions(+)
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 340e7df..d608be5 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -6,6 +6,7 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
+ android:usesCleartextTraffic="true"
android:supportsRtl="true"
android:theme="@style/AppTheme">
diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml
index b5ad138..aeba359 100644
--- a/android/app/src/main/res/layout/activity_main.xml
+++ b/android/app/src/main/res/layout/activity_main.xml
@@ -7,6 +7,7 @@
tools:context=".MainActivity">