craft-native 0.0.55 → 0.0.72

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 (146) hide show
  1. package/dist/android/src/index.d.ts +73 -0
  2. package/dist/android/src/index.js +177 -37
  3. package/dist/android/templates/AndroidManifest.xml.template +10 -36
  4. package/dist/android/templates/CraftBridge.kt.template +192 -60
  5. package/dist/android/templates/CraftHealthConnect.kt.template +200 -0
  6. package/dist/android/templates/CraftHealthConnectStub.kt.template +28 -0
  7. package/dist/android/templates/LocationRecordingService.kt.template +160 -0
  8. package/dist/android/templates/MainActivity.kt.template +140 -19
  9. package/dist/android/templates/build.gradle.kts.app.template +23 -1
  10. package/dist/android/templates/build.gradle.kts.project.template +3 -2
  11. package/dist/android/templates/proguard-rules.pro.template +9 -0
  12. package/dist/api/android-advanced.d.ts +5 -6
  13. package/dist/api/app.d.ts +0 -1
  14. package/dist/api/clipboard.d.ts +0 -1
  15. package/dist/api/crypto.d.ts +1 -2
  16. package/dist/api/db.d.ts +1 -2
  17. package/dist/api/dialog.d.ts +0 -1
  18. package/dist/api/fs.d.ts +1 -2
  19. package/dist/api/gestures.d.ts +66 -0
  20. package/dist/api/http.d.ts +1 -2
  21. package/dist/api/index.d.ts +37 -34
  22. package/dist/api/ios-advanced.d.ts +6 -7
  23. package/dist/api/macos-advanced.d.ts +2 -3
  24. package/dist/api/media.d.ts +0 -1
  25. package/dist/api/mobile.d.ts +109 -1
  26. package/dist/api/process.d.ts +0 -1
  27. package/dist/api/sidebar.d.ts +3 -20
  28. package/dist/api/spaces.d.ts +83 -0
  29. package/dist/api/tray.d.ts +2 -3
  30. package/dist/api/window.d.ts +1 -2
  31. package/dist/api/windows-advanced.d.ts +6 -7
  32. package/dist/assets/index.d.ts +0 -1
  33. package/dist/binary-resolver.d.ts +23 -1
  34. package/dist/bridge/android.d.ts +0 -1
  35. package/dist/bridge/core.d.ts +0 -38
  36. package/dist/bridge/ids.d.ts +0 -1
  37. package/dist/bridge/ios.d.ts +0 -1
  38. package/dist/bridge/webkit-pending.d.ts +0 -1
  39. package/dist/build/bundle-optimizer.d.ts +0 -1
  40. package/dist/cli.js +666 -90
  41. package/dist/components/index.d.ts +6 -7
  42. package/dist/components/native.d.ts +0 -1
  43. package/dist/components/sidebar.d.ts +2 -81
  44. package/dist/dev/devtools.d.ts +0 -1
  45. package/dist/dev/error-overlay.d.ts +0 -1
  46. package/dist/dev/hot-reload.d.ts +0 -1
  47. package/dist/index.cjs +113 -3
  48. package/dist/index.d.cts +16 -16
  49. package/dist/index.d.ts +16 -16
  50. package/dist/index.js +112 -4
  51. package/dist/ios/src/index.d.ts +172 -0
  52. package/dist/ios/src/index.js +440 -42
  53. package/dist/ios/templates/CraftActivityAttributes.swift +14 -0
  54. package/dist/ios/templates/CraftApp.swift +1001 -73
  55. package/dist/ios/templates/CraftLiveActivityWidget.swift.template +62 -0
  56. package/dist/ios/templates/CraftWatchApp.swift.template +155 -0
  57. package/dist/ios/templates/Info.plist.template +10 -9
  58. package/dist/ios/templates/WatchApp.Info.plist.template +26 -0
  59. package/dist/ios/templates/WidgetExtension.Info.plist +31 -0
  60. package/dist/ios/templates/project.yml.template +12 -1
  61. package/dist/logging/index.d.ts +0 -1
  62. package/dist/mobile.d.ts +8 -0
  63. package/dist/mobile.js +755 -0
  64. package/dist/optimizations/index.d.ts +3 -4
  65. package/dist/package.d.ts +11 -1
  66. package/dist/performance/runtime.d.ts +0 -1
  67. package/dist/performance/startup.d.ts +0 -1
  68. package/dist/plugins/index.d.ts +0 -1
  69. package/dist/security/index.d.ts +0 -1
  70. package/dist/sidebar-types.d.ts +102 -0
  71. package/dist/signing/index.d.ts +0 -1
  72. package/dist/styles/headwind.d.ts +0 -1
  73. package/dist/styles/index.d.ts +9 -10
  74. package/dist/styles/sidebar-templates.d.ts +0 -1
  75. package/dist/styles/sidebars.d.ts +0 -1
  76. package/dist/types.d.ts +131 -1
  77. package/dist/updater/index.d.ts +0 -1
  78. package/dist/utils/audio.d.ts +0 -1
  79. package/dist/utils/index.d.ts +4 -5
  80. package/dist/utils/native.d.ts +1 -2
  81. package/dist/utils/react.d.ts +0 -1
  82. package/dist/utils/storage.d.ts +0 -1
  83. package/dist/utils/svelte.d.ts +0 -1
  84. package/dist/utils/timer.d.ts +0 -1
  85. package/dist/utils/vue.d.ts +0 -1
  86. package/package.json +32 -4
  87. package/dist/android/src/index.js.map +0 -10
  88. package/dist/api/android-advanced.d.ts.map +0 -1
  89. package/dist/api/app.d.ts.map +0 -1
  90. package/dist/api/clipboard.d.ts.map +0 -1
  91. package/dist/api/crypto.d.ts.map +0 -1
  92. package/dist/api/db.d.ts.map +0 -1
  93. package/dist/api/dialog.d.ts.map +0 -1
  94. package/dist/api/fs.d.ts.map +0 -1
  95. package/dist/api/http.d.ts.map +0 -1
  96. package/dist/api/index.d.ts.map +0 -1
  97. package/dist/api/ios-advanced.d.ts.map +0 -1
  98. package/dist/api/macos-advanced.d.ts.map +0 -1
  99. package/dist/api/media.d.ts.map +0 -1
  100. package/dist/api/mobile.d.ts.map +0 -1
  101. package/dist/api/process.d.ts.map +0 -1
  102. package/dist/api/sidebar.d.ts.map +0 -1
  103. package/dist/api/tray.d.ts.map +0 -1
  104. package/dist/api/window.d.ts.map +0 -1
  105. package/dist/api/windows-advanced.d.ts.map +0 -1
  106. package/dist/assets/index.d.ts.map +0 -1
  107. package/dist/binary-resolver.d.ts.map +0 -1
  108. package/dist/bridge/android.d.ts.map +0 -1
  109. package/dist/bridge/core.d.ts.map +0 -1
  110. package/dist/bridge/ids.d.ts.map +0 -1
  111. package/dist/bridge/ios.d.ts.map +0 -1
  112. package/dist/bridge/webkit-pending.d.ts.map +0 -1
  113. package/dist/build/bundle-optimizer.d.ts.map +0 -1
  114. package/dist/components/index.d.ts.map +0 -1
  115. package/dist/components/native.d.ts.map +0 -1
  116. package/dist/components/sidebar.d.ts.map +0 -1
  117. package/dist/dev/devtools.d.ts.map +0 -1
  118. package/dist/dev/error-overlay.d.ts.map +0 -1
  119. package/dist/dev/hot-reload.d.ts.map +0 -1
  120. package/dist/index.d.ts.map +0 -1
  121. package/dist/index.js.map +0 -45
  122. package/dist/ios/src/index.js.map +0 -10
  123. package/dist/logging/index.d.ts.map +0 -1
  124. package/dist/optimizations/index.d.ts.map +0 -1
  125. package/dist/package.d.ts.map +0 -1
  126. package/dist/package.test.d.ts +0 -2
  127. package/dist/package.test.d.ts.map +0 -1
  128. package/dist/performance/runtime.d.ts.map +0 -1
  129. package/dist/performance/startup.d.ts.map +0 -1
  130. package/dist/plugins/index.d.ts.map +0 -1
  131. package/dist/security/index.d.ts.map +0 -1
  132. package/dist/signing/index.d.ts.map +0 -1
  133. package/dist/styles/headwind.d.ts.map +0 -1
  134. package/dist/styles/index.d.ts.map +0 -1
  135. package/dist/styles/sidebar-templates.d.ts.map +0 -1
  136. package/dist/styles/sidebars.d.ts.map +0 -1
  137. package/dist/types.d.ts.map +0 -1
  138. package/dist/updater/index.d.ts.map +0 -1
  139. package/dist/utils/audio.d.ts.map +0 -1
  140. package/dist/utils/index.d.ts.map +0 -1
  141. package/dist/utils/native.d.ts.map +0 -1
  142. package/dist/utils/react.d.ts.map +0 -1
  143. package/dist/utils/storage.d.ts.map +0 -1
  144. package/dist/utils/svelte.d.ts.map +0 -1
  145. package/dist/utils/timer.d.ts.map +0 -1
  146. package/dist/utils/vue.d.ts.map +0 -1
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Craft Android Builder
3
+ *
4
+ * Generates native Android apps from web content using WebView.
5
+ */
6
+ export interface CraftAndroidConfig {
7
+ appName: string;
8
+ packageName: string;
9
+ version?: string;
10
+ versionCode?: number;
11
+ darkMode?: boolean;
12
+ backgroundColor?: string;
13
+ enableSpeechRecognition?: boolean;
14
+ enableHaptics?: boolean;
15
+ enableShare?: boolean;
16
+ enableCamera?: boolean;
17
+ enableBiometric?: boolean;
18
+ enablePushNotifications?: boolean;
19
+ enableSecureStorage?: boolean;
20
+ enableGeolocation?: boolean;
21
+ enableBackgroundLocation?: boolean;
22
+ enableHealthConnect?: boolean;
23
+ enableKeepAwake?: boolean;
24
+ enableDeepLinks?: boolean;
25
+ urlSchemes?: string[];
26
+ trustedOrigins?: string[];
27
+ appIconPath?: string;
28
+ googleServicesFile?: string;
29
+ devServerURL?: string;
30
+ hasBundledFallback?: boolean;
31
+ minSdk?: number;
32
+ compileSdk?: number;
33
+ targetSdk?: number;
34
+ }
35
+ export interface InitOptions {
36
+ name: string;
37
+ packageName?: string;
38
+ output: string;
39
+ config?: Partial<CraftAndroidConfig>;
40
+ }
41
+ export interface BuildOptions {
42
+ htmlPath?: string;
43
+ devServer?: string;
44
+ output: string;
45
+ release?: boolean;
46
+ compile?: boolean;
47
+ }
48
+ export interface OpenOptions {
49
+ output: string;
50
+ }
51
+ export interface RunOptions {
52
+ device?: string;
53
+ output: string;
54
+ }
55
+ export declare function syncAndroidWebAssets(source: string, output: string): void;
56
+ export declare function renderAndroidPermissions(config: CraftAndroidConfig): string;
57
+ export declare function renderAndroidDeepLinks(config: CraftAndroidConfig): string;
58
+ /**
59
+ * Initialize a new Android project
60
+ */
61
+ export declare function init(options: InitOptions): Promise<void>;
62
+ /**
63
+ * Build Android project
64
+ */
65
+ export declare function build(options: BuildOptions): Promise<void>;
66
+ /**
67
+ * Open Android project in Android Studio
68
+ */
69
+ export declare function open(options: OpenOptions): Promise<void>;
70
+ /**
71
+ * Run on Android device or emulator
72
+ */
73
+ export declare function run(options: RunOptions): Promise<void>;
@@ -1,9 +1,98 @@
1
1
  // @bun
2
2
  // ../android/src/index.ts
3
- import { readFileSync, writeFileSync, mkdirSync, existsSync, statSync } from "fs";
4
- import { join, dirname } from "path";
3
+ import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
4
+ import { dirname, join, resolve } from "path";
5
5
  var {$ } = globalThis.Bun;
6
6
  var TEMPLATES_DIR = join(dirname(import.meta.dir), "templates");
7
+ var DEFAULT_CONFIG = {
8
+ version: "1.0.0",
9
+ versionCode: 1,
10
+ darkMode: true,
11
+ backgroundColor: "#0b1712",
12
+ enableSpeechRecognition: false,
13
+ enableHaptics: false,
14
+ enableShare: false,
15
+ enableCamera: false,
16
+ enableBiometric: false,
17
+ enablePushNotifications: false,
18
+ enableSecureStorage: false,
19
+ enableGeolocation: false,
20
+ enableBackgroundLocation: false,
21
+ enableHealthConnect: false,
22
+ enableKeepAwake: false,
23
+ enableDeepLinks: false,
24
+ urlSchemes: [],
25
+ trustedOrigins: [],
26
+ minSdk: 26,
27
+ compileSdk: 36,
28
+ targetSdk: 35
29
+ };
30
+ function syncAndroidWebAssets(source, output) {
31
+ const sourcePath = resolve(source);
32
+ if (!existsSync(sourcePath))
33
+ throw new Error(`Web asset path not found: ${source}`);
34
+ const assetsDir = join(output, "app/src/main/assets");
35
+ const configPath = join(assetsDir, "craft.config.json");
36
+ const config = existsSync(configPath) ? readFileSync(configPath) : undefined;
37
+ rmSync(assetsDir, { recursive: true, force: true });
38
+ mkdirSync(assetsDir, { recursive: true });
39
+ if (statSync(sourcePath).isDirectory())
40
+ cpSync(sourcePath, assetsDir, { recursive: true });
41
+ else
42
+ cpSync(sourcePath, join(assetsDir, "index.html"));
43
+ if (!existsSync(join(assetsDir, "index.html")))
44
+ throw new Error(`Web asset directory must contain index.html: ${source}`);
45
+ if (config)
46
+ writeFileSync(configPath, config);
47
+ }
48
+ function renderAndroidPermissions(config) {
49
+ const permissions = new Set(["android.permission.INTERNET", "android.permission.ACCESS_NETWORK_STATE"]);
50
+ if (config.enableSpeechRecognition)
51
+ permissions.add("android.permission.RECORD_AUDIO");
52
+ if (config.enableCamera)
53
+ permissions.add("android.permission.CAMERA");
54
+ if (config.enableHaptics)
55
+ permissions.add("android.permission.VIBRATE");
56
+ if (config.enableBiometric)
57
+ permissions.add("android.permission.USE_BIOMETRIC");
58
+ if (config.enableGeolocation || config.enableBackgroundLocation) {
59
+ permissions.add("android.permission.ACCESS_FINE_LOCATION");
60
+ permissions.add("android.permission.ACCESS_COARSE_LOCATION");
61
+ }
62
+ if (config.enableBackgroundLocation) {
63
+ permissions.add("android.permission.ACCESS_BACKGROUND_LOCATION");
64
+ permissions.add("android.permission.FOREGROUND_SERVICE");
65
+ permissions.add("android.permission.FOREGROUND_SERVICE_LOCATION");
66
+ }
67
+ if (config.enablePushNotifications)
68
+ permissions.add("android.permission.POST_NOTIFICATIONS");
69
+ if (config.enableKeepAwake)
70
+ permissions.add("android.permission.WAKE_LOCK");
71
+ if (config.enableHealthConnect) {
72
+ permissions.add("android.permission.health.READ_STEPS");
73
+ permissions.add("android.permission.health.READ_HEART_RATE");
74
+ permissions.add("android.permission.health.READ_ACTIVE_CALORIES_BURNED");
75
+ permissions.add("android.permission.health.READ_DISTANCE");
76
+ permissions.add("android.permission.health.READ_EXERCISE");
77
+ permissions.add("android.permission.health.WRITE_EXERCISE");
78
+ permissions.add("android.permission.health.WRITE_ACTIVE_CALORIES_BURNED");
79
+ permissions.add("android.permission.health.WRITE_DISTANCE");
80
+ }
81
+ return [...permissions].map((permission) => ` <uses-permission android:name="${permission}" />`).join(`
82
+ `);
83
+ }
84
+ function renderAndroidDeepLinks(config) {
85
+ if (!config.enableDeepLinks)
86
+ return "";
87
+ const schemes = [...new Set(config.urlSchemes?.map((value) => value.trim()).filter((value) => /^[a-z][a-z0-9+.-]*$/i.test(value)) ?? [])];
88
+ return schemes.map((scheme) => ` <intent-filter>
89
+ <action android:name="android.intent.action.VIEW" />
90
+ <category android:name="android.intent.category.DEFAULT" />
91
+ <category android:name="android.intent.category.BROWSABLE" />
92
+ <data android:scheme="${scheme}" />
93
+ </intent-filter>`).join(`
94
+ `);
95
+ }
7
96
  async function init(options) {
8
97
  const { name, packageName, output } = options;
9
98
  console.log(`
@@ -27,36 +116,73 @@ async function init(options) {
27
116
  }
28
117
  }
29
118
  const config = {
119
+ ...DEFAULT_CONFIG,
30
120
  appName: name,
31
121
  packageName: finalPackageName,
32
- version: "1.0.0",
33
- versionCode: 1,
34
- darkMode: true,
35
- backgroundColor: "#1a1a2e",
36
- enableSpeechRecognition: true,
37
- enableHaptics: true,
38
- enableShare: true,
39
- enableCamera: true,
40
- enableBiometric: true,
41
- enablePushNotifications: false,
42
- minSdk: 24,
43
- targetSdk: 34
122
+ ...options.config
44
123
  };
124
+ if (config.enableBackgroundLocation)
125
+ config.enableGeolocation = true;
45
126
  writeFileSync(join(output, "craft.config.json"), JSON.stringify(config, null, 2));
127
+ writeFileSync(join(output, "app/src/main/assets/craft.config.json"), JSON.stringify(config, null, 2));
128
+ const hasGoogleServices = Boolean(config.googleServicesFile);
129
+ if (config.googleServicesFile) {
130
+ if (!existsSync(config.googleServicesFile))
131
+ throw new Error(`Google services file not found: ${config.googleServicesFile}`);
132
+ cpSync(config.googleServicesFile, join(output, "app/google-services.json"));
133
+ }
46
134
  const mainActivityTemplate = readFileSync(join(TEMPLATES_DIR, "MainActivity.kt.template"), "utf-8");
47
135
  const mainActivity = mainActivityTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name);
48
136
  writeFileSync(join(output, "app/src/main/java", packagePath, "MainActivity.kt"), mainActivity);
49
137
  const craftBridgeTemplate = readFileSync(join(TEMPLATES_DIR, "CraftBridge.kt.template"), "utf-8");
50
- const craftBridge = craftBridgeTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName);
138
+ const craftBridge = craftBridgeTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{ENABLE_SPEECH\}\}/g, String(Boolean(config.enableSpeechRecognition))).replace(/\{\{ENABLE_HAPTICS\}\}/g, String(Boolean(config.enableHaptics))).replace(/\{\{ENABLE_SHARE\}\}/g, String(Boolean(config.enableShare))).replace(/\{\{ENABLE_CAMERA\}\}/g, String(Boolean(config.enableCamera))).replace(/\{\{ENABLE_BIOMETRIC\}\}/g, String(Boolean(config.enableBiometric))).replace(/\{\{ENABLE_PUSH\}\}/g, String(Boolean(config.enablePushNotifications))).replace(/\{\{ENABLE_SECURE_STORAGE\}\}/g, String(Boolean(config.enableSecureStorage))).replace(/\{\{ENABLE_GEOLOCATION\}\}/g, String(Boolean(config.enableGeolocation))).replace(/\{\{ENABLE_BACKGROUND_LOCATION\}\}/g, String(Boolean(config.enableBackgroundLocation))).replace(/\{\{ENABLE_KEEP_AWAKE\}\}/g, String(Boolean(config.enableKeepAwake))).replace(/\{\{ENABLE_DEEP_LINKS\}\}/g, String(Boolean(config.enableDeepLinks))).replace(/\{\{ENABLE_HEALTH_CONNECT\}\}/g, String(Boolean(config.enableHealthConnect))).replace(/\{\{FIREBASE_IMPORT\}\}/g, config.enablePushNotifications ? "import com.google.firebase.messaging.FirebaseMessaging" : "").replace(/\{\{REGISTER_PUSH_IMPLEMENTATION\}\}/g, config.enablePushNotifications ? `activity.runOnUiThread {
139
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
140
+ && ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
141
+ ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 4204)
142
+ }
143
+ try {
144
+ FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
145
+ val token = if (task.isSuccessful) task.result else null
146
+ val callback = if (token.isNullOrBlank()) "window._craftPushReject" else "window._craftPushResolve"
147
+ val payload = JSONObject.quote(token ?: task.exception?.message ?: "Firebase Cloud Messaging is not configured")
148
+ webView.evaluateJavascript("$callback && $callback($payload)", null)
149
+ }
150
+ } catch (error: Exception) {
151
+ val payload = JSONObject.quote(error.message ?: "Firebase Cloud Messaging is not configured")
152
+ webView.evaluateJavascript("window._craftPushReject && window._craftPushReject($payload)", null)
153
+ }
154
+ }` : `activity.runOnUiThread {
155
+ webView.evaluateJavascript(
156
+ "window._craftPushReject && window._craftPushReject('Push notifications are disabled')",
157
+ null
158
+ )
159
+ }`);
51
160
  writeFileSync(join(output, "app/src/main/java", packagePath, "CraftBridge.kt"), craftBridge);
161
+ const healthTemplate = readFileSync(join(TEMPLATES_DIR, config.enableHealthConnect ? "CraftHealthConnect.kt.template" : "CraftHealthConnectStub.kt.template"), "utf-8");
162
+ writeFileSync(join(output, "app/src/main/java", packagePath, "CraftHealthConnect.kt"), healthTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
163
+ if (config.enableBackgroundLocation) {
164
+ const serviceTemplate = readFileSync(join(TEMPLATES_DIR, "LocationRecordingService.kt.template"), "utf-8");
165
+ writeFileSync(join(output, "app/src/main/java", packagePath, "LocationRecordingService.kt"), serviceTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
166
+ }
52
167
  const manifestTemplate = readFileSync(join(TEMPLATES_DIR, "AndroidManifest.xml.template"), "utf-8");
53
- const manifest = manifestTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name);
168
+ const manifest = manifestTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{PERMISSIONS\}\}/g, renderAndroidPermissions(config)).replace(/\{\{USES_CLEARTEXT\}\}/g, config.devServerURL?.startsWith("http://") ? "true" : "false").replace(/\{\{DEEP_LINK_INTENT_FILTERS\}\}/g, renderAndroidDeepLinks(config)).replace(/\{\{BACKGROUND_SERVICE\}\}/g, config.enableBackgroundLocation ? ' <service android:name=".LocationRecordingService" android:exported="false" android:foregroundServiceType="location" android:stopWithTask="false" />' : "").replace(/\{\{HEALTH_CONNECT_QUERIES\}\}/g, config.enableHealthConnect ? ` <queries>
169
+ <package android:name="com.google.android.apps.healthdata" />
170
+ </queries>` : "").replace(/\{\{HEALTH_CONNECT_RATIONALE\}\}/g, config.enableHealthConnect ? ` <intent-filter>
171
+ <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
172
+ </intent-filter>
173
+ <intent-filter>
174
+ <action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
175
+ <category android:name="android.intent.category.HEALTH_PERMISSIONS" />
176
+ </intent-filter>` : "");
54
177
  writeFileSync(join(output, "app/src/main/AndroidManifest.xml"), manifest);
55
178
  const projectGradleTemplate = readFileSync(join(TEMPLATES_DIR, "build.gradle.kts.project.template"), "utf-8");
56
- writeFileSync(join(output, "build.gradle.kts"), projectGradleTemplate);
179
+ writeFileSync(join(output, "build.gradle.kts"), projectGradleTemplate.replace(/\{\{GOOGLE_SERVICES_PLUGIN\}\}/g, hasGoogleServices ? ' id("com.google.gms.google-services") version "4.4.2" apply false' : ""));
57
180
  const appGradleTemplate = readFileSync(join(TEMPLATES_DIR, "build.gradle.kts.app.template"), "utf-8");
58
- const appGradle = appGradleTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{VERSION_NAME\}\}/g, config.version || "1.0.0").replace(/\{\{VERSION_CODE\}\}/g, String(config.versionCode || 1)).replace(/\{\{MIN_SDK\}\}/g, String(config.minSdk || 24)).replace(/\{\{TARGET_SDK\}\}/g, String(config.targetSdk || 34));
181
+ const appGradle = appGradleTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{VERSION_NAME\}\}/g, config.version || "1.0.0").replace(/\{\{VERSION_CODE\}\}/g, String(config.versionCode || 1)).replace(/\{\{MIN_SDK\}\}/g, String(config.minSdk || 24)).replace(/\{\{COMPILE_SDK\}\}/g, String(Math.max(config.compileSdk || 36, config.enableHealthConnect ? 36 : 1))).replace(/\{\{TARGET_SDK\}\}/g, String(config.targetSdk || 35)).replace(/\{\{GOOGLE_SERVICES_PLUGIN\}\}/g, hasGoogleServices ? ' id("com.google.gms.google-services")' : "").replace(/\{\{FIREBASE_MESSAGING_DEPENDENCY\}\}/g, config.enablePushNotifications ? ' implementation("com.google.firebase:firebase-messaging:24.1.0")' : "").replace(/\{\{HEALTH_CONNECT_DEPENDENCIES\}\}/g, config.enableHealthConnect ? ` implementation("androidx.health.connect:connect-client:1.1.0")
182
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")` : "");
59
183
  writeFileSync(join(output, "app/build.gradle.kts"), appGradle);
184
+ const proguardTemplate = readFileSync(join(TEMPLATES_DIR, "proguard-rules.pro.template"), "utf-8");
185
+ writeFileSync(join(output, "app/proguard-rules.pro"), proguardTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
60
186
  const settingsTemplate = readFileSync(join(TEMPLATES_DIR, "settings.gradle.kts.template"), "utf-8");
61
187
  const settings = settingsTemplate.replace(/\{\{APP_NAME\}\}/g, name);
62
188
  writeFileSync(join(output, "settings.gradle.kts"), settings);
@@ -70,7 +196,7 @@ android.nonTransitiveRClass=true
70
196
  `);
71
197
  const gradleWrapperProps = `distributionBase=GRADLE_USER_HOME
72
198
  distributionPath=wrapper/dists
73
- distributionUrl=https\\://services.gradle.org/distributions/gradle-8.4-bin.zip
199
+ distributionUrl=https\\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
74
200
  networkTimeout=10000
75
201
  validateDistributionUrl=true
76
202
  zipStoreBase=GRADLE_USER_HOME
@@ -92,6 +218,23 @@ zipStorePath=wrapper/dists
92
218
  </resources>
93
219
  `;
94
220
  writeFileSync(join(output, "app/src/main/res/values/colors.xml"), colorsXml);
221
+ const appIconXml = `<?xml version="1.0" encoding="utf-8"?>
222
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
223
+ android:width="108dp"
224
+ android:height="108dp"
225
+ android:viewportWidth="108"
226
+ android:viewportHeight="108">
227
+ <path android:fillColor="${config.backgroundColor}" android:pathData="M0,0h108v108h-108z" />
228
+ <path android:fillColor="#10B981" android:pathData="M18,78L42,42l12,18l12,-12l24,30z" />
229
+ </vector>
230
+ `;
231
+ if (config.appIconPath) {
232
+ if (!existsSync(config.appIconPath))
233
+ throw new Error(`App icon not found: ${config.appIconPath}`);
234
+ cpSync(config.appIconPath, join(output, "app/src/main/res/drawable/craft_app_icon.png"));
235
+ } else {
236
+ writeFileSync(join(output, "app/src/main/res/drawable/craft_app_icon.xml"), appIconXml);
237
+ }
95
238
  const themesXml = `<?xml version="1.0" encoding="utf-8"?>
96
239
  <resources>
97
240
  <style name="Theme.CraftApp" parent="Theme.Material3.DayNight.NoActionBar">
@@ -165,7 +308,7 @@ zipStorePath=wrapper/dists
165
308
  console.log("");
166
309
  }
167
310
  async function build(options) {
168
- const { htmlPath, devServer, output, release } = options;
311
+ const { htmlPath, devServer, output, release, compile = true } = options;
169
312
  console.log(`
170
313
  \uD83D\uDCE6 Building Craft Android project...`);
171
314
  const configPath = join(output, "craft.config.json");
@@ -175,31 +318,27 @@ async function build(options) {
175
318
  const config = JSON.parse(readFileSync(configPath, "utf-8"));
176
319
  if (devServer) {
177
320
  config.devServerURL = devServer;
321
+ config.trustedOrigins = [...new Set([...config.trustedOrigins ?? [], new URL(devServer).origin])];
178
322
  writeFileSync(configPath, JSON.stringify(config, null, 2));
323
+ writeFileSync(join(output, "app/src/main/assets/craft.config.json"), JSON.stringify(config, null, 2));
179
324
  console.log(` Dev server: ${devServer}`);
180
325
  }
181
326
  if (htmlPath) {
182
- const assetsDir = join(output, "app/src/main/assets");
183
- if (!existsSync(assetsDir)) {
184
- mkdirSync(assetsDir, { recursive: true });
185
- }
186
- if (existsSync(htmlPath) && statSync(htmlPath).isFile()) {
187
- const html = readFileSync(htmlPath, "utf-8");
188
- writeFileSync(join(assetsDir, "index.html"), html);
189
- console.log(` Copied: ${htmlPath} \u2192 assets/index.html`);
190
- } else {
191
- throw new Error(`HTML file not found or is a directory: ${htmlPath}`);
192
- }
327
+ syncAndroidWebAssets(htmlPath, output);
328
+ config.hasBundledFallback = Boolean(devServer);
329
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
330
+ writeFileSync(join(output, "app/src/main/assets/craft.config.json"), JSON.stringify(config, null, 2));
331
+ console.log(` Synced: ${htmlPath} \u2192 assets/`);
193
332
  }
333
+ if (!compile)
334
+ return;
194
335
  const gradlewPath = join(output, "gradlew");
195
336
  if (!existsSync(gradlewPath)) {
196
337
  console.log(" Generating Gradle wrapper...");
197
338
  try {
198
339
  await $`cd ${output} && gradle wrapper`.quiet();
199
- } catch {
200
- console.log("\u26A0\uFE0F Gradle not found. Please install Android Studio or Gradle.");
201
- console.log(" Download: https://developer.android.com/studio");
202
- return;
340
+ } catch (error) {
341
+ throw new Error("Gradle is unavailable. Install Android Studio or Gradle before building Android.", { cause: error });
203
342
  }
204
343
  }
205
344
  const buildType = release ? "assembleRelease" : "assembleDebug";
@@ -259,10 +398,11 @@ async function run(options) {
259
398
  }
260
399
  }
261
400
  export {
401
+ syncAndroidWebAssets,
262
402
  run,
403
+ renderAndroidPermissions,
404
+ renderAndroidDeepLinks,
263
405
  open,
264
406
  init,
265
407
  build
266
408
  };
267
-
268
- //# debugId=5497ABB54764526564756E2164756E21
@@ -2,38 +2,8 @@
2
2
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
3
  xmlns:tools="http://schemas.android.com/tools">
4
4
 
5
- <!-- Permissions -->
6
- <uses-permission android:name="android.permission.INTERNET" />
7
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
8
- <uses-permission android:name="android.permission.RECORD_AUDIO" />
9
- <uses-permission android:name="android.permission.CAMERA" />
10
- <uses-permission android:name="android.permission.VIBRATE" />
11
- <uses-permission android:name="android.permission.USE_BIOMETRIC" />
12
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
13
- android:maxSdkVersion="32" />
14
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
15
- android:maxSdkVersion="28" />
16
-
17
- <!-- Location permissions -->
18
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
19
- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
20
-
21
- <!-- Flashlight permission -->
22
- <uses-permission android:name="android.permission.FLASHLIGHT" />
23
-
24
- <!-- Contacts permissions -->
25
- <uses-permission android:name="android.permission.READ_CONTACTS" />
26
- <uses-permission android:name="android.permission.WRITE_CONTACTS" />
27
-
28
- <!-- Calendar permissions -->
29
- <uses-permission android:name="android.permission.READ_CALENDAR" />
30
- <uses-permission android:name="android.permission.WRITE_CALENDAR" />
31
-
32
- <!-- Notifications permission (Android 13+) -->
33
- <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
34
-
35
- <!-- Keep screen on -->
36
- <uses-permission android:name="android.permission.WAKE_LOCK" />
5
+ {{PERMISSIONS}}
6
+ {{HEALTH_CONNECT_QUERIES}}
37
7
 
38
8
  <!-- Camera feature (optional) -->
39
9
  <uses-feature android:name="android.hardware.camera" android:required="false" />
@@ -42,13 +12,13 @@
42
12
  <uses-feature android:name="android.hardware.location.gps" android:required="false" />
43
13
 
44
14
  <application
45
- android:allowBackup="true"
46
- android:icon="@mipmap/ic_launcher"
15
+ android:allowBackup="false"
16
+ android:icon="@drawable/craft_app_icon"
47
17
  android:label="@string/app_name"
48
- android:roundIcon="@mipmap/ic_launcher_round"
18
+ android:roundIcon="@drawable/craft_app_icon"
49
19
  android:supportsRtl="true"
50
20
  android:theme="@style/Theme.CraftApp"
51
- android:usesCleartextTraffic="true"
21
+ android:usesCleartextTraffic="{{USES_CLEARTEXT}}"
52
22
  android:hardwareAccelerated="true"
53
23
  tools:targetApi="34">
54
24
 
@@ -62,8 +32,12 @@
62
32
  <action android:name="android.intent.action.MAIN" />
63
33
  <category android:name="android.intent.category.LAUNCHER" />
64
34
  </intent-filter>
35
+ {{DEEP_LINK_INTENT_FILTERS}}
36
+ {{HEALTH_CONNECT_RATIONALE}}
65
37
  </activity>
66
38
 
39
+ {{BACKGROUND_SERVICE}}
40
+
67
41
  </application>
68
42
 
69
43
  </manifest>