craft-native 0.0.37 → 0.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/dist/android/src/index.js +268 -0
  2. package/dist/android/src/index.js.map +10 -0
  3. package/dist/android/templates/AndroidManifest.xml.template +69 -0
  4. package/dist/android/templates/CraftBridge.kt.template +3748 -0
  5. package/dist/android/templates/CraftBridgeExtensions.kt.template +383 -0
  6. package/dist/android/templates/CraftWidgetProvider.kt.template +246 -0
  7. package/dist/android/templates/MainActivity.kt.template +109 -0
  8. package/dist/android/templates/build.gradle.kts.app.template +80 -0
  9. package/dist/android/templates/build.gradle.kts.project.template +5 -0
  10. package/dist/android/templates/fastlane/Appfile +25 -0
  11. package/dist/android/templates/fastlane/Fastfile +192 -0
  12. package/dist/android/templates/fastlane/Gemfile +7 -0
  13. package/dist/android/templates/github-workflow-android.yml +248 -0
  14. package/dist/android/templates/github-workflow-test.yml +329 -0
  15. package/dist/android/templates/settings.gradle.kts.template +18 -0
  16. package/dist/android/templates/test-bridges.html +1463 -0
  17. package/dist/api/window.d.ts +18 -0
  18. package/dist/api/window.d.ts.map +1 -1
  19. package/dist/assets/index.d.ts.map +1 -1
  20. package/dist/bridge/core.d.ts +2 -0
  21. package/dist/bridge/core.d.ts.map +1 -1
  22. package/dist/cli.js +4168 -0
  23. package/dist/components/sidebar.d.ts +5 -1
  24. package/dist/components/sidebar.d.ts.map +1 -1
  25. package/dist/dev/devtools.d.ts +1 -1
  26. package/dist/dev/devtools.d.ts.map +1 -1
  27. package/dist/dev/hot-reload.d.ts +12 -3
  28. package/dist/dev/hot-reload.d.ts.map +1 -1
  29. package/dist/index.cjs +573 -208
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +585 -216
  32. package/dist/index.js.map +45 -0
  33. package/dist/ios/src/index.js +186 -0
  34. package/dist/ios/src/index.js.map +10 -0
  35. package/dist/ios/templates/CraftApp.swift +4241 -0
  36. package/dist/ios/templates/CraftAppExtensions.swift +361 -0
  37. package/dist/ios/templates/CraftWidget.swift +247 -0
  38. package/dist/ios/templates/Info.plist.template +65 -0
  39. package/dist/ios/templates/fastlane/Appfile +45 -0
  40. package/dist/ios/templates/fastlane/Fastfile +189 -0
  41. package/dist/ios/templates/fastlane/Gemfile +7 -0
  42. package/dist/ios/templates/fastlane/Matchfile +58 -0
  43. package/dist/ios/templates/github-workflow-ios.yml +192 -0
  44. package/dist/ios/templates/github-workflow-release.yml +175 -0
  45. package/dist/ios/templates/github-workflow-test.yml +272 -0
  46. package/dist/ios/templates/project.yml.template +25 -0
  47. package/dist/ios/templates/test-bridges.html +1463 -0
  48. package/dist/package.d.ts +106 -2
  49. package/dist/package.d.ts.map +1 -1
  50. package/dist/package.test.d.ts +2 -0
  51. package/dist/package.test.d.ts.map +1 -0
  52. package/dist/styles/sidebar-templates.d.ts.map +1 -1
  53. package/dist/styles/sidebars.d.ts +12 -8
  54. package/dist/styles/sidebars.d.ts.map +1 -1
  55. package/dist/templates/projects/blank/craft.config.ts +19 -0
  56. package/dist/templates/projects/blank/index.html +27 -0
  57. package/dist/templates/projects/blank/package.json +19 -0
  58. package/dist/templates/projects/blank/src/main.ts +26 -0
  59. package/dist/templates/projects/dashboard/craft.config.ts +42 -0
  60. package/dist/templates/projects/dashboard/index.html +13 -0
  61. package/dist/templates/projects/dashboard/package.json +20 -0
  62. package/dist/templates/projects/dashboard/src/main.ts +163 -0
  63. package/dist/templates/projects/dashboard/src/styles.css +280 -0
  64. package/dist/templates/projects/drawer/craft.config.ts +47 -0
  65. package/dist/templates/projects/drawer/index.html +13 -0
  66. package/dist/templates/projects/drawer/package.json +21 -0
  67. package/dist/templates/projects/drawer/src/main.ts +406 -0
  68. package/dist/templates/projects/drawer/src/styles.css +468 -0
  69. package/dist/templates/projects/ecommerce/craft.config.ts +32 -0
  70. package/dist/templates/projects/ecommerce/index.html +13 -0
  71. package/dist/templates/projects/ecommerce/package.json +19 -0
  72. package/dist/templates/projects/ecommerce/src/main.ts +194 -0
  73. package/dist/templates/projects/ecommerce/src/styles.css +169 -0
  74. package/dist/templates/projects/social/craft.config.ts +32 -0
  75. package/dist/templates/projects/social/index.html +13 -0
  76. package/dist/templates/projects/social/package.json +19 -0
  77. package/dist/templates/projects/social/src/main.ts +270 -0
  78. package/dist/templates/projects/social/src/styles.css +216 -0
  79. package/dist/templates/projects/tabs/craft.config.ts +47 -0
  80. package/dist/templates/projects/tabs/index.html +13 -0
  81. package/dist/templates/projects/tabs/package.json +21 -0
  82. package/dist/templates/projects/tabs/src/main.ts +177 -0
  83. package/dist/templates/projects/tabs/src/styles.css +163 -0
  84. package/dist/types.d.ts +29 -0
  85. package/dist/types.d.ts.map +1 -1
  86. package/dist/updater/index.d.ts +1 -0
  87. package/dist/updater/index.d.ts.map +1 -1
  88. package/package.json +5 -12
  89. package/bin/cli.ts +0 -937
@@ -0,0 +1,109 @@
1
+ package {{PACKAGE_NAME}}
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.os.Bundle
5
+ import android.webkit.WebChromeClient
6
+ import android.webkit.WebSettings
7
+ import android.webkit.WebView
8
+ import android.webkit.WebViewClient
9
+ import androidx.appcompat.app.AppCompatActivity
10
+ import androidx.core.view.WindowCompat
11
+ import java.io.BufferedReader
12
+ import java.io.InputStreamReader
13
+
14
+ class MainActivity : AppCompatActivity() {
15
+ private lateinit var webView: WebView
16
+ private lateinit var craftBridge: CraftBridge
17
+
18
+ @SuppressLint("SetJavaScriptEnabled")
19
+ override fun onCreate(savedInstanceState: Bundle?) {
20
+ super.onCreate(savedInstanceState)
21
+
22
+ // Enable edge-to-edge
23
+ WindowCompat.setDecorFitsSystemWindows(window, false)
24
+
25
+ setContentView(R.layout.activity_main)
26
+
27
+ webView = findViewById(R.id.webview)
28
+ craftBridge = CraftBridge(this, webView)
29
+
30
+ setupWebView()
31
+ loadContent()
32
+ }
33
+
34
+ @SuppressLint("SetJavaScriptEnabled")
35
+ private fun setupWebView() {
36
+ webView.settings.apply {
37
+ javaScriptEnabled = true
38
+ domStorageEnabled = true
39
+ allowFileAccess = true
40
+ allowContentAccess = true
41
+ mediaPlaybackRequiresUserGesture = false
42
+ mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
43
+ cacheMode = WebSettings.LOAD_DEFAULT
44
+ setSupportZoom(false)
45
+ builtInZoomControls = false
46
+ displayZoomControls = false
47
+ }
48
+
49
+ webView.webViewClient = object : WebViewClient() {
50
+ override fun onPageFinished(view: WebView?, url: String?) {
51
+ super.onPageFinished(view, url)
52
+ craftBridge.injectBridge()
53
+ }
54
+ }
55
+
56
+ webView.webChromeClient = WebChromeClient()
57
+
58
+ // Add JavaScript interface
59
+ webView.addJavascriptInterface(craftBridge, "CraftAndroid")
60
+ }
61
+
62
+ private fun loadContent() {
63
+ // Check for dev server URL in config
64
+ val devServerUrl = getDevServerUrl()
65
+ if (devServerUrl != null) {
66
+ webView.loadUrl(devServerUrl)
67
+ } else {
68
+ // Load from assets
69
+ webView.loadUrl("file:///android_asset/index.html")
70
+ }
71
+ }
72
+
73
+ private fun getDevServerUrl(): String? {
74
+ return try {
75
+ val inputStream = assets.open("craft.config.json")
76
+ val reader = BufferedReader(InputStreamReader(inputStream))
77
+ val content = reader.readText()
78
+ reader.close()
79
+ // Simple JSON parsing for devServerURL
80
+ val regex = "\"devServerURL\"\\s*:\\s*\"([^\"]+)\"".toRegex()
81
+ regex.find(content)?.groupValues?.get(1)
82
+ } catch (e: Exception) {
83
+ null
84
+ }
85
+ }
86
+
87
+ override fun onBackPressed() {
88
+ if (webView.canGoBack()) {
89
+ webView.goBack()
90
+ } else {
91
+ super.onBackPressed()
92
+ }
93
+ }
94
+
95
+ override fun onResume() {
96
+ super.onResume()
97
+ webView.onResume()
98
+ }
99
+
100
+ override fun onPause() {
101
+ webView.onPause()
102
+ super.onPause()
103
+ }
104
+
105
+ override fun onDestroy() {
106
+ webView.destroy()
107
+ super.onDestroy()
108
+ }
109
+ }
@@ -0,0 +1,80 @@
1
+ plugins {
2
+ id("com.android.application")
3
+ id("org.jetbrains.kotlin.android")
4
+ }
5
+
6
+ android {
7
+ namespace = "{{PACKAGE_NAME}}"
8
+ compileSdk = {{TARGET_SDK}}
9
+
10
+ defaultConfig {
11
+ applicationId = "{{PACKAGE_NAME}}"
12
+ minSdk = {{MIN_SDK}}
13
+ targetSdk = {{TARGET_SDK}}
14
+ versionCode = {{VERSION_CODE}}
15
+ versionName = "{{VERSION_NAME}}"
16
+
17
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18
+ }
19
+
20
+ buildTypes {
21
+ release {
22
+ isMinifyEnabled = true
23
+ isShrinkResources = true
24
+ proguardFiles(
25
+ getDefaultProguardFile("proguard-android-optimize.txt"),
26
+ "proguard-rules.pro"
27
+ )
28
+ }
29
+ debug {
30
+ isMinifyEnabled = false
31
+ }
32
+ }
33
+
34
+ compileOptions {
35
+ sourceCompatibility = JavaVersion.VERSION_17
36
+ targetCompatibility = JavaVersion.VERSION_17
37
+ }
38
+
39
+ kotlinOptions {
40
+ jvmTarget = "17"
41
+ }
42
+
43
+ buildFeatures {
44
+ viewBinding = true
45
+ }
46
+ }
47
+
48
+ dependencies {
49
+ // AndroidX Core
50
+ implementation("androidx.core:core-ktx:1.12.0")
51
+ implementation("androidx.appcompat:appcompat:1.6.1")
52
+ implementation("com.google.android.material:material:1.11.0")
53
+ implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0")
54
+
55
+ // Biometric
56
+ implementation("androidx.biometric:biometric:1.1.0")
57
+
58
+ // Security / Encrypted SharedPreferences
59
+ implementation("androidx.security:security-crypto:1.1.0-alpha06")
60
+
61
+ // Lifecycle (for app state monitoring)
62
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
63
+ implementation("androidx.lifecycle:lifecycle-process:2.7.0")
64
+
65
+ // Google Play Location Services (for geolocation)
66
+ implementation("com.google.android.gms:play-services-location:21.1.0")
67
+
68
+ // Google Play In-App Review
69
+ implementation("com.google.android.play:review:2.0.1")
70
+ implementation("com.google.android.play:review-ktx:2.0.1")
71
+
72
+ // Google Play Billing (In-App Purchase)
73
+ implementation("com.android.billingclient:billing:6.1.0")
74
+ implementation("com.android.billingclient:billing-ktx:6.1.0")
75
+
76
+ // Testing
77
+ testImplementation("junit:junit:4.13.2")
78
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
79
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
80
+ }
@@ -0,0 +1,5 @@
1
+ // Top-level build file where you can add configuration options common to all sub-projects/modules.
2
+ plugins {
3
+ id("com.android.application") version "8.2.0" apply false
4
+ id("org.jetbrains.kotlin.android") version "1.9.20" apply false
5
+ }
@@ -0,0 +1,25 @@
1
+ # Android Fastlane Appfile
2
+ # Stores Google Play Store configuration
3
+
4
+ # Your app's package name
5
+ # package_name("com.yourcompany.craftapp")
6
+
7
+ # Path to your Google Play JSON key file
8
+ # json_key_file("./play-store-key.json")
9
+
10
+ # ==================== Example Configuration ====================
11
+ # Uncomment and modify:
12
+
13
+ # package_name("com.yourcompany.craftapp")
14
+ # json_key_file("./play-store-key.json")
15
+
16
+ # ==================== Setting up Google Play API ====================
17
+ # 1. Go to Google Play Console > Setup > API access
18
+ # 2. Create a service account (or link existing)
19
+ # 3. Grant "Release Manager" permissions to the service account
20
+ # 4. Download the JSON key file
21
+ # 5. Store the JSON key securely (add to .gitignore)
22
+ #
23
+ # For CI/CD:
24
+ # - Store JSON key content as a secret (PLAY_STORE_JSON_KEY)
25
+ # - In CI, decode and save: echo "$PLAY_STORE_JSON_KEY" > play-store-key.json
@@ -0,0 +1,192 @@
1
+ # Craft Android Fastlane Configuration
2
+ # Copy this directory to your android/ folder
3
+ # Run: cd android && bundle install && bundle exec fastlane <lane>
4
+
5
+ default_platform(:android)
6
+
7
+ platform :android do
8
+ # ==================== Build ====================
9
+
10
+ desc "Build Debug APK"
11
+ lane :build_debug do
12
+ gradle(task: "assembleDebug")
13
+ end
14
+
15
+ desc "Build Release APK"
16
+ lane :build_release do
17
+ gradle(
18
+ task: "assembleRelease",
19
+ properties: {
20
+ "android.injected.signing.store.file" => ENV["KEYSTORE_PATH"] || "release.keystore",
21
+ "android.injected.signing.store.password" => ENV["KEYSTORE_PASSWORD"],
22
+ "android.injected.signing.key.alias" => ENV["KEY_ALIAS"],
23
+ "android.injected.signing.key.password" => ENV["KEY_PASSWORD"]
24
+ }
25
+ )
26
+ end
27
+
28
+ desc "Build Release Bundle (AAB)"
29
+ lane :build_bundle do
30
+ gradle(
31
+ task: "bundleRelease",
32
+ properties: {
33
+ "android.injected.signing.store.file" => ENV["KEYSTORE_PATH"] || "release.keystore",
34
+ "android.injected.signing.store.password" => ENV["KEYSTORE_PASSWORD"],
35
+ "android.injected.signing.key.alias" => ENV["KEY_ALIAS"],
36
+ "android.injected.signing.key.password" => ENV["KEY_PASSWORD"]
37
+ }
38
+ )
39
+ end
40
+
41
+ # ==================== Deploy ====================
42
+
43
+ desc "Deploy to Play Store Internal Track"
44
+ lane :internal do
45
+ build_bundle
46
+
47
+ upload_to_play_store(
48
+ track: "internal",
49
+ aab: "app/build/outputs/bundle/release/app-release.aab",
50
+ skip_upload_metadata: true,
51
+ skip_upload_images: true,
52
+ skip_upload_screenshots: true
53
+ )
54
+ end
55
+
56
+ desc "Deploy to Play Store Alpha Track"
57
+ lane :alpha do
58
+ build_bundle
59
+
60
+ upload_to_play_store(
61
+ track: "alpha",
62
+ aab: "app/build/outputs/bundle/release/app-release.aab",
63
+ skip_upload_metadata: true,
64
+ skip_upload_images: true,
65
+ skip_upload_screenshots: true
66
+ )
67
+ end
68
+
69
+ desc "Deploy to Play Store Beta Track"
70
+ lane :beta do
71
+ build_bundle
72
+
73
+ upload_to_play_store(
74
+ track: "beta",
75
+ aab: "app/build/outputs/bundle/release/app-release.aab",
76
+ skip_upload_metadata: true,
77
+ skip_upload_images: true,
78
+ skip_upload_screenshots: true
79
+ )
80
+ end
81
+
82
+ desc "Deploy to Play Store Production"
83
+ lane :release do
84
+ build_bundle
85
+
86
+ upload_to_play_store(
87
+ track: "production",
88
+ aab: "app/build/outputs/bundle/release/app-release.aab",
89
+ skip_upload_metadata: false,
90
+ skip_upload_images: true,
91
+ skip_upload_screenshots: true,
92
+ rollout: "0.1" # 10% rollout, increase manually
93
+ )
94
+ end
95
+
96
+ # ==================== Testing ====================
97
+
98
+ desc "Run Unit Tests"
99
+ lane :test do
100
+ gradle(task: "testDebugUnitTest")
101
+ end
102
+
103
+ desc "Run Instrumented Tests"
104
+ lane :instrumented_tests do
105
+ gradle(task: "connectedDebugAndroidTest")
106
+ end
107
+
108
+ desc "Run Lint"
109
+ lane :lint do
110
+ gradle(task: "lint")
111
+ end
112
+
113
+ # ==================== Version Management ====================
114
+
115
+ desc "Increment version code"
116
+ lane :bump_version_code do
117
+ # Read current version code
118
+ version_code = android_get_version_code(gradle_file: "app/build.gradle")
119
+ new_version_code = version_code.to_i + 1
120
+
121
+ # Update version code
122
+ android_set_version_code(
123
+ version_code: new_version_code,
124
+ gradle_file: "app/build.gradle"
125
+ )
126
+
127
+ UI.success("Version code bumped to #{new_version_code}")
128
+ end
129
+
130
+ desc "Set version name"
131
+ lane :set_version do |options|
132
+ version = options[:version] || prompt(text: "Version name: ")
133
+
134
+ android_set_version_name(
135
+ version_name: version,
136
+ gradle_file: "app/build.gradle"
137
+ )
138
+
139
+ UI.success("Version name set to #{version}")
140
+ end
141
+
142
+ # ==================== Keystore Management ====================
143
+
144
+ desc "Generate release keystore"
145
+ lane :generate_keystore do
146
+ store_password = prompt(text: "Keystore password: ", secure_text: true)
147
+ key_alias = prompt(text: "Key alias: ")
148
+ key_password = prompt(text: "Key password: ", secure_text: true)
149
+ full_name = prompt(text: "Full name (CN): ")
150
+ org = prompt(text: "Organization (O): ")
151
+ country = prompt(text: "Country code (C): ")
152
+
153
+ sh(
154
+ "keytool -genkey -v -keystore release.keystore " \
155
+ "-alias #{key_alias} -keyalg RSA -keysize 2048 -validity 10000 " \
156
+ "-storepass #{store_password} -keypass #{key_password} " \
157
+ "-dname \"CN=#{full_name}, O=#{org}, C=#{country}\""
158
+ )
159
+
160
+ UI.success("Keystore generated at release.keystore")
161
+ UI.important("Store this file and passwords securely!")
162
+ UI.important("Add to .gitignore: *.keystore")
163
+ end
164
+
165
+ # ==================== Utilities ====================
166
+
167
+ desc "Clean build"
168
+ lane :clean do
169
+ gradle(task: "clean")
170
+ UI.success("Build cleaned!")
171
+ end
172
+
173
+ # ==================== CI/CD ====================
174
+
175
+ desc "CI build and test"
176
+ lane :ci do
177
+ lint
178
+ test
179
+ build_release
180
+ end
181
+
182
+ desc "CI deploy to internal track"
183
+ lane :ci_internal do
184
+ # Decode keystore from base64 environment variable
185
+ if ENV["KEYSTORE_BASE64"]
186
+ sh("echo $KEYSTORE_BASE64 | base64 -d > release.keystore")
187
+ end
188
+
189
+ bump_version_code
190
+ internal
191
+ end
192
+ end
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "fastlane", "~> 2.217"
4
+
5
+ # Plugins
6
+ plugins_path = File.join(File.dirname(__FILE__), 'Pluginfile')
7
+ eval_gemfile(plugins_path) if File.exist?(plugins_path)
@@ -0,0 +1,248 @@
1
+ # Android Build and Test Workflow
2
+ # Copy this file to your project's .github/workflows/android.yml
3
+
4
+ name: Android Build
5
+
6
+ on:
7
+ push:
8
+ branches: [main, develop]
9
+ paths:
10
+ - 'android/**'
11
+ - '*.kt'
12
+ - '*.java'
13
+ - 'gradle/**'
14
+ - '*.gradle*'
15
+ pull_request:
16
+ branches: [main]
17
+ paths:
18
+ - 'android/**'
19
+ - '*.kt'
20
+ - '*.java'
21
+ - 'gradle/**'
22
+ - '*.gradle*'
23
+ workflow_dispatch:
24
+
25
+ env:
26
+ JAVA_VERSION: '17'
27
+ GRADLE_VERSION: '8.2'
28
+
29
+ jobs:
30
+ build:
31
+ name: Build Android App
32
+ runs-on: ubuntu-latest
33
+
34
+ steps:
35
+ - name: Checkout
36
+ uses: actions/checkout@v4
37
+
38
+ - name: Set up JDK
39
+ uses: actions/setup-java@v4
40
+ with:
41
+ java-version: ${{ env.JAVA_VERSION }}
42
+ distribution: 'temurin'
43
+ cache: 'gradle'
44
+
45
+ - name: Grant execute permission for gradlew
46
+ run: chmod +x android/gradlew
47
+
48
+ - name: Run Lint
49
+ working-directory: android
50
+ run: ./gradlew lint
51
+
52
+ - name: Build Debug APK
53
+ working-directory: android
54
+ run: ./gradlew assembleDebug
55
+
56
+ - name: Run Unit Tests
57
+ working-directory: android
58
+ run: ./gradlew testDebugUnitTest
59
+
60
+ - name: Upload Debug APK
61
+ uses: actions/upload-artifact@v4
62
+ with:
63
+ name: debug-apk
64
+ path: android/app/build/outputs/apk/debug/*.apk
65
+ retention-days: 7
66
+
67
+ - name: Upload Lint Results
68
+ if: always()
69
+ uses: actions/upload-artifact@v4
70
+ with:
71
+ name: lint-results
72
+ path: android/app/build/reports/lint-results*.html
73
+ retention-days: 7
74
+
75
+ - name: Upload Test Results
76
+ if: always()
77
+ uses: actions/upload-artifact@v4
78
+ with:
79
+ name: test-results
80
+ path: android/app/build/reports/tests/
81
+ retention-days: 7
82
+
83
+ build-release:
84
+ name: Build Release
85
+ runs-on: ubuntu-latest
86
+ needs: build
87
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
88
+
89
+ steps:
90
+ - name: Checkout
91
+ uses: actions/checkout@v4
92
+
93
+ - name: Set up JDK
94
+ uses: actions/setup-java@v4
95
+ with:
96
+ java-version: ${{ env.JAVA_VERSION }}
97
+ distribution: 'temurin'
98
+ cache: 'gradle'
99
+
100
+ - name: Grant execute permission for gradlew
101
+ run: chmod +x android/gradlew
102
+
103
+ - name: Build Release APK (unsigned)
104
+ working-directory: android
105
+ run: ./gradlew assembleRelease
106
+
107
+ - name: Build Release Bundle (unsigned)
108
+ working-directory: android
109
+ run: ./gradlew bundleRelease
110
+
111
+ - name: Upload Release APK
112
+ uses: actions/upload-artifact@v4
113
+ with:
114
+ name: release-apk
115
+ path: android/app/build/outputs/apk/release/*.apk
116
+ retention-days: 7
117
+
118
+ - name: Upload Release Bundle
119
+ uses: actions/upload-artifact@v4
120
+ with:
121
+ name: release-bundle
122
+ path: android/app/build/outputs/bundle/release/*.aab
123
+ retention-days: 7
124
+
125
+ # Optional: Signed build for Play Store
126
+ # Requires: KEYSTORE_BASE64, KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD secrets
127
+ build-signed:
128
+ name: Build Signed Release
129
+ runs-on: ubuntu-latest
130
+ needs: build
131
+ if: false # Set to true when you have signing configured
132
+
133
+ steps:
134
+ - name: Checkout
135
+ uses: actions/checkout@v4
136
+
137
+ - name: Set up JDK
138
+ uses: actions/setup-java@v4
139
+ with:
140
+ java-version: ${{ env.JAVA_VERSION }}
141
+ distribution: 'temurin'
142
+ cache: 'gradle'
143
+
144
+ - name: Grant execute permission for gradlew
145
+ run: chmod +x android/gradlew
146
+
147
+ - name: Decode Keystore
148
+ env:
149
+ KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
150
+ run: |
151
+ echo "$KEYSTORE_BASE64" | base64 --decode > android/app/release.keystore
152
+
153
+ - name: Build Signed Release APK
154
+ working-directory: android
155
+ env:
156
+ KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
157
+ KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
158
+ KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
159
+ run: |
160
+ ./gradlew assembleRelease \
161
+ -Pandroid.injected.signing.store.file=release.keystore \
162
+ -Pandroid.injected.signing.store.password="$KEYSTORE_PASSWORD" \
163
+ -Pandroid.injected.signing.key.alias="$KEY_ALIAS" \
164
+ -Pandroid.injected.signing.key.password="$KEY_PASSWORD"
165
+
166
+ - name: Build Signed Release Bundle
167
+ working-directory: android
168
+ env:
169
+ KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
170
+ KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
171
+ KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
172
+ run: |
173
+ ./gradlew bundleRelease \
174
+ -Pandroid.injected.signing.store.file=release.keystore \
175
+ -Pandroid.injected.signing.store.password="$KEYSTORE_PASSWORD" \
176
+ -Pandroid.injected.signing.key.alias="$KEY_ALIAS" \
177
+ -Pandroid.injected.signing.key.password="$KEY_PASSWORD"
178
+
179
+ - name: Upload Signed APK
180
+ uses: actions/upload-artifact@v4
181
+ with:
182
+ name: signed-apk
183
+ path: android/app/build/outputs/apk/release/*.apk
184
+ retention-days: 7
185
+
186
+ - name: Upload Signed Bundle
187
+ uses: actions/upload-artifact@v4
188
+ with:
189
+ name: signed-bundle
190
+ path: android/app/build/outputs/bundle/release/*.aab
191
+ retention-days: 7
192
+
193
+ - name: Cleanup Keystore
194
+ if: always()
195
+ run: rm -f android/app/release.keystore
196
+
197
+ # Instrumented tests on emulator
198
+ instrumented-tests:
199
+ name: Run Instrumented Tests
200
+ runs-on: ubuntu-latest
201
+ needs: build
202
+ if: false # Enable when you have instrumented tests
203
+
204
+ steps:
205
+ - name: Checkout
206
+ uses: actions/checkout@v4
207
+
208
+ - name: Set up JDK
209
+ uses: actions/setup-java@v4
210
+ with:
211
+ java-version: ${{ env.JAVA_VERSION }}
212
+ distribution: 'temurin'
213
+ cache: 'gradle'
214
+
215
+ - name: Enable KVM
216
+ run: |
217
+ echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
218
+ sudo udevadm control --reload-rules
219
+ sudo udevadm trigger --name-match=kvm
220
+
221
+ - name: AVD cache
222
+ uses: actions/cache@v4
223
+ id: avd-cache
224
+ with:
225
+ path: |
226
+ ~/.android/avd/*
227
+ ~/.android/adb*
228
+ key: avd-29-default
229
+
230
+ - name: Create AVD and generate snapshot
231
+ if: steps.avd-cache.outputs.cache-hit != 'true'
232
+ uses: reactivecircus/android-emulator-runner@v2
233
+ with:
234
+ api-level: 29
235
+ force-avd-creation: false
236
+ emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
237
+ disable-animations: true
238
+ script: echo "Generated AVD snapshot for caching."
239
+
240
+ - name: Run Instrumented Tests
241
+ uses: reactivecircus/android-emulator-runner@v2
242
+ with:
243
+ api-level: 29
244
+ force-avd-creation: false
245
+ emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
246
+ disable-animations: true
247
+ working-directory: android
248
+ script: ./gradlew connectedDebugAndroidTest