@craft-native/android 0.0.72 → 0.0.75
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.
- package/LICENSE.md +21 -0
- package/package.json +1 -1
- package/dist/index.js +0 -410
- package/dist/index.js.map +0 -10
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Stacks.js
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
CHANGED
package/dist/index.js
DELETED
|
@@ -1,410 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// src/index.ts
|
|
3
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
|
|
4
|
-
import { dirname, join, resolve } from "path";
|
|
5
|
-
var {$ } = globalThis.Bun;
|
|
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
|
-
}
|
|
96
|
-
async function init(options) {
|
|
97
|
-
const { name, packageName, output } = options;
|
|
98
|
-
console.log(`
|
|
99
|
-
\u26A1 Initializing Craft Android project: ${name}`);
|
|
100
|
-
console.log(` Output: ${output}
|
|
101
|
-
`);
|
|
102
|
-
const finalPackageName = packageName || `com.craft.${name.toLowerCase().replace(/[^a-z0-9]/g, "")}`;
|
|
103
|
-
const packagePath = finalPackageName.replace(/\./g, "/");
|
|
104
|
-
const dirs = [
|
|
105
|
-
output,
|
|
106
|
-
join(output, "app/src/main/java", packagePath),
|
|
107
|
-
join(output, "app/src/main/res/layout"),
|
|
108
|
-
join(output, "app/src/main/res/values"),
|
|
109
|
-
join(output, "app/src/main/res/drawable"),
|
|
110
|
-
join(output, "app/src/main/assets"),
|
|
111
|
-
join(output, "gradle/wrapper")
|
|
112
|
-
];
|
|
113
|
-
for (const dir of dirs) {
|
|
114
|
-
if (!existsSync(dir)) {
|
|
115
|
-
mkdirSync(dir, { recursive: true });
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
const config = {
|
|
119
|
-
...DEFAULT_CONFIG,
|
|
120
|
-
appName: name,
|
|
121
|
-
packageName: finalPackageName,
|
|
122
|
-
...options.config
|
|
123
|
-
};
|
|
124
|
-
if (config.enableBackgroundLocation)
|
|
125
|
-
config.enableGeolocation = true;
|
|
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
|
-
}
|
|
134
|
-
const mainActivityTemplate = readFileSync(join(TEMPLATES_DIR, "MainActivity.kt.template"), "utf-8");
|
|
135
|
-
const mainActivity = mainActivityTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name);
|
|
136
|
-
writeFileSync(join(output, "app/src/main/java", packagePath, "MainActivity.kt"), mainActivity);
|
|
137
|
-
const craftBridgeTemplate = readFileSync(join(TEMPLATES_DIR, "CraftBridge.kt.template"), "utf-8");
|
|
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
|
-
}`);
|
|
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
|
-
}
|
|
167
|
-
const manifestTemplate = readFileSync(join(TEMPLATES_DIR, "AndroidManifest.xml.template"), "utf-8");
|
|
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>` : "");
|
|
177
|
-
writeFileSync(join(output, "app/src/main/AndroidManifest.xml"), manifest);
|
|
178
|
-
const projectGradleTemplate = readFileSync(join(TEMPLATES_DIR, "build.gradle.kts.project.template"), "utf-8");
|
|
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' : ""));
|
|
180
|
-
const appGradleTemplate = readFileSync(join(TEMPLATES_DIR, "build.gradle.kts.app.template"), "utf-8");
|
|
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")` : "");
|
|
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));
|
|
186
|
-
const settingsTemplate = readFileSync(join(TEMPLATES_DIR, "settings.gradle.kts.template"), "utf-8");
|
|
187
|
-
const settings = settingsTemplate.replace(/\{\{APP_NAME\}\}/g, name);
|
|
188
|
-
writeFileSync(join(output, "settings.gradle.kts"), settings);
|
|
189
|
-
const gradleProperties = `org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|
190
|
-
android.useAndroidX=true
|
|
191
|
-
kotlin.code.style=official
|
|
192
|
-
android.nonTransitiveRClass=true
|
|
193
|
-
`;
|
|
194
|
-
writeFileSync(join(output, "gradle.properties"), gradleProperties);
|
|
195
|
-
writeFileSync(join(output, "local.properties"), `# SDK location will be set by Android Studio
|
|
196
|
-
`);
|
|
197
|
-
const gradleWrapperProps = `distributionBase=GRADLE_USER_HOME
|
|
198
|
-
distributionPath=wrapper/dists
|
|
199
|
-
distributionUrl=https\\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
|
200
|
-
networkTimeout=10000
|
|
201
|
-
validateDistributionUrl=true
|
|
202
|
-
zipStoreBase=GRADLE_USER_HOME
|
|
203
|
-
zipStorePath=wrapper/dists
|
|
204
|
-
`;
|
|
205
|
-
writeFileSync(join(output, "gradle/wrapper/gradle-wrapper.properties"), gradleWrapperProps);
|
|
206
|
-
const stringsXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
207
|
-
<resources>
|
|
208
|
-
<string name="app_name">${name}</string>
|
|
209
|
-
</resources>
|
|
210
|
-
`;
|
|
211
|
-
writeFileSync(join(output, "app/src/main/res/values/strings.xml"), stringsXml);
|
|
212
|
-
const colorsXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
213
|
-
<resources>
|
|
214
|
-
<color name="primary">#1a1a2e</color>
|
|
215
|
-
<color name="primary_dark">#16162b</color>
|
|
216
|
-
<color name="accent">#4ade80</color>
|
|
217
|
-
<color name="background">${config.backgroundColor}</color>
|
|
218
|
-
</resources>
|
|
219
|
-
`;
|
|
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
|
-
}
|
|
238
|
-
const themesXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
239
|
-
<resources>
|
|
240
|
-
<style name="Theme.CraftApp" parent="Theme.Material3.DayNight.NoActionBar">
|
|
241
|
-
<item name="android:statusBarColor">@color/primary_dark</item>
|
|
242
|
-
<item name="android:navigationBarColor">@color/primary</item>
|
|
243
|
-
<item name="android:windowBackground">@color/background</item>
|
|
244
|
-
</style>
|
|
245
|
-
</resources>
|
|
246
|
-
`;
|
|
247
|
-
writeFileSync(join(output, "app/src/main/res/values/themes.xml"), themesXml);
|
|
248
|
-
const activityMainXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
249
|
-
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
250
|
-
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
251
|
-
android:layout_width="match_parent"
|
|
252
|
-
android:layout_height="match_parent"
|
|
253
|
-
android:fitsSystemWindows="true">
|
|
254
|
-
|
|
255
|
-
<WebView
|
|
256
|
-
android:id="@+id/webview"
|
|
257
|
-
android:layout_width="match_parent"
|
|
258
|
-
android:layout_height="match_parent" />
|
|
259
|
-
|
|
260
|
-
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
261
|
-
`;
|
|
262
|
-
writeFileSync(join(output, "app/src/main/res/layout/activity_main.xml"), activityMainXml);
|
|
263
|
-
const placeholderHtml = `<!DOCTYPE html>
|
|
264
|
-
<html>
|
|
265
|
-
<head>
|
|
266
|
-
<meta charset="UTF-8">
|
|
267
|
-
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
268
|
-
<title>${name}</title>
|
|
269
|
-
<style>
|
|
270
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
271
|
-
body {
|
|
272
|
-
font-family: 'Roboto', sans-serif;
|
|
273
|
-
background: ${config.backgroundColor};
|
|
274
|
-
color: white;
|
|
275
|
-
min-height: 100vh;
|
|
276
|
-
display: flex;
|
|
277
|
-
justify-content: center;
|
|
278
|
-
align-items: center;
|
|
279
|
-
}
|
|
280
|
-
.container { text-align: center; padding: 2rem; }
|
|
281
|
-
h1 { font-size: 2.5rem; margin-bottom: 1rem; }
|
|
282
|
-
p { opacity: 0.7; }
|
|
283
|
-
.ready { color: #4ade80; font-size: 0.9rem; margin-top: 2rem; }
|
|
284
|
-
</style>
|
|
285
|
-
</head>
|
|
286
|
-
<body>
|
|
287
|
-
<div class="container">
|
|
288
|
-
<h1>\u26A1 ${name}</h1>
|
|
289
|
-
<p>Built with Craft Android</p>
|
|
290
|
-
<p class="ready" id="status">Waiting for Craft bridge...</p>
|
|
291
|
-
</div>
|
|
292
|
-
<script>
|
|
293
|
-
window.addEventListener('craftReady', (e) => {
|
|
294
|
-
document.getElementById('status').textContent = \`\u2713 Craft bridge ready (platform: \${e.detail.platform})\`;
|
|
295
|
-
console.log('Craft capabilities:', e.detail.capabilities);
|
|
296
|
-
});
|
|
297
|
-
</script>
|
|
298
|
-
</body>
|
|
299
|
-
</html>`;
|
|
300
|
-
writeFileSync(join(output, "app/src/main/assets/index.html"), placeholderHtml);
|
|
301
|
-
console.log("\u2705 Project initialized");
|
|
302
|
-
console.log("");
|
|
303
|
-
console.log("Next steps:");
|
|
304
|
-
console.log(` 1. cd ${output}`);
|
|
305
|
-
console.log(" 2. Add your web content to app/src/main/assets/index.html");
|
|
306
|
-
console.log(" 3. Run: craft android build");
|
|
307
|
-
console.log(" 4. Run: craft android open");
|
|
308
|
-
console.log("");
|
|
309
|
-
}
|
|
310
|
-
async function build(options) {
|
|
311
|
-
const { htmlPath, devServer, output, release, compile = true } = options;
|
|
312
|
-
console.log(`
|
|
313
|
-
\uD83D\uDCE6 Building Craft Android project...`);
|
|
314
|
-
const configPath = join(output, "craft.config.json");
|
|
315
|
-
if (!existsSync(configPath)) {
|
|
316
|
-
throw new Error(`No craft.config.json found in ${output}. Run 'craft android init' first.`);
|
|
317
|
-
}
|
|
318
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
319
|
-
if (devServer) {
|
|
320
|
-
config.devServerURL = devServer;
|
|
321
|
-
config.trustedOrigins = [...new Set([...config.trustedOrigins ?? [], new URL(devServer).origin])];
|
|
322
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
323
|
-
writeFileSync(join(output, "app/src/main/assets/craft.config.json"), JSON.stringify(config, null, 2));
|
|
324
|
-
console.log(` Dev server: ${devServer}`);
|
|
325
|
-
}
|
|
326
|
-
if (htmlPath) {
|
|
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/`);
|
|
332
|
-
}
|
|
333
|
-
if (!compile)
|
|
334
|
-
return;
|
|
335
|
-
const gradlewPath = join(output, "gradlew");
|
|
336
|
-
if (!existsSync(gradlewPath)) {
|
|
337
|
-
console.log(" Generating Gradle wrapper...");
|
|
338
|
-
try {
|
|
339
|
-
await $`cd ${output} && gradle wrapper`.quiet();
|
|
340
|
-
} catch (error) {
|
|
341
|
-
throw new Error("Gradle is unavailable. Install Android Studio or Gradle before building Android.", { cause: error });
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
const buildType = release ? "assembleRelease" : "assembleDebug";
|
|
345
|
-
console.log(` Building ${release ? "release" : "debug"} APK...`);
|
|
346
|
-
try {
|
|
347
|
-
await $`cd ${output} && ./gradlew ${buildType}`;
|
|
348
|
-
const apkPath = release ? "app/build/outputs/apk/release/app-release.apk" : "app/build/outputs/apk/debug/app-debug.apk";
|
|
349
|
-
console.log(`\u2705 APK built: ${apkPath}`);
|
|
350
|
-
} catch (error) {
|
|
351
|
-
console.error("Build failed. Open in Android Studio for details.");
|
|
352
|
-
throw error;
|
|
353
|
-
}
|
|
354
|
-
console.log("");
|
|
355
|
-
}
|
|
356
|
-
async function open(options) {
|
|
357
|
-
const { output } = options;
|
|
358
|
-
if (!existsSync(output)) {
|
|
359
|
-
throw new Error(`No Android project found in ${output}. Run 'craft android init' first.`);
|
|
360
|
-
}
|
|
361
|
-
console.log(`\uD83D\uDE80 Opening Android project in Android Studio...`);
|
|
362
|
-
try {
|
|
363
|
-
await $`open -a "Android Studio" ${output}`.quiet();
|
|
364
|
-
} catch {
|
|
365
|
-
try {
|
|
366
|
-
await $`studio ${output}`.quiet();
|
|
367
|
-
} catch {
|
|
368
|
-
console.log("\u26A0\uFE0F Android Studio not found.");
|
|
369
|
-
console.log(" Please open the project manually in Android Studio:");
|
|
370
|
-
console.log(` ${output}`);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
async function run(options) {
|
|
375
|
-
const { device, output } = options;
|
|
376
|
-
await build({ output });
|
|
377
|
-
console.log("\uD83D\uDCF1 Installing and running on device...");
|
|
378
|
-
try {
|
|
379
|
-
const apkPath = join(output, "app/build/outputs/apk/debug/app-debug.apk");
|
|
380
|
-
if (device) {
|
|
381
|
-
await $`adb -s ${device} install -r ${apkPath}`;
|
|
382
|
-
} else {
|
|
383
|
-
await $`adb install -r ${apkPath}`;
|
|
384
|
-
}
|
|
385
|
-
const config = JSON.parse(readFileSync(join(output, "craft.config.json"), "utf-8"));
|
|
386
|
-
const launchCmd = `${config.packageName}/.MainActivity`;
|
|
387
|
-
if (device) {
|
|
388
|
-
await $`adb -s ${device} shell am start -n ${launchCmd}`;
|
|
389
|
-
} else {
|
|
390
|
-
await $`adb shell am start -n ${launchCmd}`;
|
|
391
|
-
}
|
|
392
|
-
console.log("\u2705 App launched");
|
|
393
|
-
} catch (error) {
|
|
394
|
-
console.error("Failed to install/run. Make sure:");
|
|
395
|
-
console.error(" 1. ADB is installed and in PATH");
|
|
396
|
-
console.error(" 2. A device/emulator is connected (run: adb devices)");
|
|
397
|
-
throw error;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
export {
|
|
401
|
-
syncAndroidWebAssets,
|
|
402
|
-
run,
|
|
403
|
-
renderAndroidPermissions,
|
|
404
|
-
renderAndroidDeepLinks,
|
|
405
|
-
open,
|
|
406
|
-
init,
|
|
407
|
-
build
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
//# debugId=F0D2BDD613AA95E164756E2164756E21
|
package/dist/index.js.map
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"/**\n * Craft Android Builder\n *\n * Generates native Android apps from web content using WebView.\n */\n\nimport { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs'\nimport { dirname, join, resolve } from 'node:path'\nimport { $ } from 'bun'\n\nconst TEMPLATES_DIR = join(dirname(import.meta.dir), 'templates')\n\nexport interface CraftAndroidConfig {\n appName: string\n packageName: string\n version?: string\n versionCode?: number\n darkMode?: boolean\n backgroundColor?: string\n enableSpeechRecognition?: boolean\n enableHaptics?: boolean\n enableShare?: boolean\n enableCamera?: boolean\n enableBiometric?: boolean\n enablePushNotifications?: boolean\n enableSecureStorage?: boolean\n enableGeolocation?: boolean\n enableBackgroundLocation?: boolean\n enableHealthConnect?: boolean\n enableKeepAwake?: boolean\n enableDeepLinks?: boolean\n urlSchemes?: string[]\n trustedOrigins?: string[]\n appIconPath?: string\n googleServicesFile?: string\n devServerURL?: string\n hasBundledFallback?: boolean\n minSdk?: number\n compileSdk?: number\n targetSdk?: number\n}\n\nexport interface InitOptions {\n name: string\n packageName?: string\n output: string\n config?: Partial<CraftAndroidConfig>\n}\n\nexport interface BuildOptions {\n htmlPath?: string\n devServer?: string\n output: string\n release?: boolean\n compile?: boolean\n}\n\nexport interface OpenOptions {\n output: string\n}\n\nexport interface RunOptions {\n device?: string\n output: string\n}\n\nconst DEFAULT_CONFIG: Omit<CraftAndroidConfig, 'appName' | 'packageName'> = {\n version: '1.0.0',\n versionCode: 1,\n darkMode: true,\n backgroundColor: '#0b1712',\n enableSpeechRecognition: false,\n enableHaptics: false,\n enableShare: false,\n enableCamera: false,\n enableBiometric: false,\n enablePushNotifications: false,\n enableSecureStorage: false,\n enableGeolocation: false,\n enableBackgroundLocation: false,\n enableHealthConnect: false,\n enableKeepAwake: false,\n enableDeepLinks: false,\n urlSchemes: [],\n trustedOrigins: [],\n minSdk: 26,\n compileSdk: 36,\n targetSdk: 35,\n}\n\nexport function syncAndroidWebAssets(source: string, output: string): void {\n const sourcePath = resolve(source)\n if (!existsSync(sourcePath)) throw new Error(`Web asset path not found: ${source}`)\n const assetsDir = join(output, 'app/src/main/assets')\n const configPath = join(assetsDir, 'craft.config.json')\n const config = existsSync(configPath) ? readFileSync(configPath) : undefined\n rmSync(assetsDir, { recursive: true, force: true })\n mkdirSync(assetsDir, { recursive: true })\n if (statSync(sourcePath).isDirectory()) cpSync(sourcePath, assetsDir, { recursive: true })\n else cpSync(sourcePath, join(assetsDir, 'index.html'))\n if (!existsSync(join(assetsDir, 'index.html'))) throw new Error(`Web asset directory must contain index.html: ${source}`)\n if (config) writeFileSync(configPath, config)\n}\n\nexport function renderAndroidPermissions(config: CraftAndroidConfig): string {\n const permissions = new Set(['android.permission.INTERNET', 'android.permission.ACCESS_NETWORK_STATE'])\n if (config.enableSpeechRecognition) permissions.add('android.permission.RECORD_AUDIO')\n if (config.enableCamera) permissions.add('android.permission.CAMERA')\n if (config.enableHaptics) permissions.add('android.permission.VIBRATE')\n if (config.enableBiometric) permissions.add('android.permission.USE_BIOMETRIC')\n if (config.enableGeolocation || config.enableBackgroundLocation) {\n permissions.add('android.permission.ACCESS_FINE_LOCATION')\n permissions.add('android.permission.ACCESS_COARSE_LOCATION')\n }\n if (config.enableBackgroundLocation) {\n permissions.add('android.permission.ACCESS_BACKGROUND_LOCATION')\n permissions.add('android.permission.FOREGROUND_SERVICE')\n permissions.add('android.permission.FOREGROUND_SERVICE_LOCATION')\n }\n if (config.enablePushNotifications) permissions.add('android.permission.POST_NOTIFICATIONS')\n if (config.enableKeepAwake) permissions.add('android.permission.WAKE_LOCK')\n if (config.enableHealthConnect) {\n permissions.add('android.permission.health.READ_STEPS')\n permissions.add('android.permission.health.READ_HEART_RATE')\n permissions.add('android.permission.health.READ_ACTIVE_CALORIES_BURNED')\n permissions.add('android.permission.health.READ_DISTANCE')\n permissions.add('android.permission.health.READ_EXERCISE')\n permissions.add('android.permission.health.WRITE_EXERCISE')\n permissions.add('android.permission.health.WRITE_ACTIVE_CALORIES_BURNED')\n permissions.add('android.permission.health.WRITE_DISTANCE')\n }\n return [...permissions].map(permission => ` <uses-permission android:name=\"${permission}\" />`).join('\\n')\n}\n\nexport function renderAndroidDeepLinks(config: CraftAndroidConfig): string {\n if (!config.enableDeepLinks) return ''\n const schemes = [...new Set(config.urlSchemes?.map(value => value.trim()).filter(value => /^[a-z][a-z0-9+.-]*$/i.test(value)) ?? [])]\n return schemes.map(scheme => ` <intent-filter>\n <action android:name=\"android.intent.action.VIEW\" />\n <category android:name=\"android.intent.category.DEFAULT\" />\n <category android:name=\"android.intent.category.BROWSABLE\" />\n <data android:scheme=\"${scheme}\" />\n </intent-filter>`).join('\\n')\n}\n\n/**\n * Initialize a new Android project\n */\nexport async function init(options: InitOptions): Promise<void> {\n const { name, packageName, output } = options\n\n console.log(`\\n⚡ Initializing Craft Android project: ${name}`)\n console.log(` Output: ${output}\\n`)\n\n // Generate package name from app name if not provided\n const finalPackageName = packageName || `com.craft.${name.toLowerCase().replace(/[^a-z0-9]/g, '')}`\n const packagePath = finalPackageName.replace(/\\./g, '/')\n\n // Create directory structure\n const dirs = [\n output,\n join(output, 'app/src/main/java', packagePath),\n join(output, 'app/src/main/res/layout'),\n join(output, 'app/src/main/res/values'),\n join(output, 'app/src/main/res/drawable'),\n join(output, 'app/src/main/assets'),\n join(output, 'gradle/wrapper'),\n ]\n\n for (const dir of dirs) {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true })\n }\n }\n\n // Create craft.config.json\n const config: CraftAndroidConfig = {\n ...DEFAULT_CONFIG,\n appName: name,\n packageName: finalPackageName,\n ...options.config,\n }\n if (config.enableBackgroundLocation) config.enableGeolocation = true\n\n writeFileSync(join(output, 'craft.config.json'), JSON.stringify(config, null, 2))\n writeFileSync(join(output, 'app/src/main/assets/craft.config.json'), JSON.stringify(config, null, 2))\n\n const hasGoogleServices = Boolean(config.googleServicesFile)\n if (config.googleServicesFile) {\n if (!existsSync(config.googleServicesFile)) throw new Error(`Google services file not found: ${config.googleServicesFile}`)\n cpSync(config.googleServicesFile, join(output, 'app/google-services.json'))\n }\n\n // Copy templates\n const mainActivityTemplate = readFileSync(join(TEMPLATES_DIR, 'MainActivity.kt.template'), 'utf-8')\n const mainActivity = mainActivityTemplate\n .replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName)\n .replace(/\\{\\{APP_NAME\\}\\}/g, name)\n\n writeFileSync(join(output, 'app/src/main/java', packagePath, 'MainActivity.kt'), mainActivity)\n\n // Create CraftBridge.kt\n const craftBridgeTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftBridge.kt.template'), 'utf-8')\n const craftBridge = craftBridgeTemplate\n .replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName)\n .replace(/\\{\\{ENABLE_SPEECH\\}\\}/g, String(Boolean(config.enableSpeechRecognition)))\n .replace(/\\{\\{ENABLE_HAPTICS\\}\\}/g, String(Boolean(config.enableHaptics)))\n .replace(/\\{\\{ENABLE_SHARE\\}\\}/g, String(Boolean(config.enableShare)))\n .replace(/\\{\\{ENABLE_CAMERA\\}\\}/g, String(Boolean(config.enableCamera)))\n .replace(/\\{\\{ENABLE_BIOMETRIC\\}\\}/g, String(Boolean(config.enableBiometric)))\n .replace(/\\{\\{ENABLE_PUSH\\}\\}/g, String(Boolean(config.enablePushNotifications)))\n .replace(/\\{\\{ENABLE_SECURE_STORAGE\\}\\}/g, String(Boolean(config.enableSecureStorage)))\n .replace(/\\{\\{ENABLE_GEOLOCATION\\}\\}/g, String(Boolean(config.enableGeolocation)))\n .replace(/\\{\\{ENABLE_BACKGROUND_LOCATION\\}\\}/g, String(Boolean(config.enableBackgroundLocation)))\n .replace(/\\{\\{ENABLE_KEEP_AWAKE\\}\\}/g, String(Boolean(config.enableKeepAwake)))\n .replace(/\\{\\{ENABLE_DEEP_LINKS\\}\\}/g, String(Boolean(config.enableDeepLinks)))\n .replace(/\\{\\{ENABLE_HEALTH_CONNECT\\}\\}/g, String(Boolean(config.enableHealthConnect)))\n .replace(/\\{\\{FIREBASE_IMPORT\\}\\}/g, config.enablePushNotifications ? 'import com.google.firebase.messaging.FirebaseMessaging' : '')\n .replace(/\\{\\{REGISTER_PUSH_IMPLEMENTATION\\}\\}/g, config.enablePushNotifications\n ? `activity.runOnUiThread {\n if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU\n && ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {\n ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 4204)\n }\n try {\n FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->\n val token = if (task.isSuccessful) task.result else null\n val callback = if (token.isNullOrBlank()) \"window._craftPushReject\" else \"window._craftPushResolve\"\n val payload = JSONObject.quote(token ?: task.exception?.message ?: \"Firebase Cloud Messaging is not configured\")\n webView.evaluateJavascript(\"$callback && $callback($payload)\", null)\n }\n } catch (error: Exception) {\n val payload = JSONObject.quote(error.message ?: \"Firebase Cloud Messaging is not configured\")\n webView.evaluateJavascript(\"window._craftPushReject && window._craftPushReject($payload)\", null)\n }\n }`\n : `activity.runOnUiThread {\n webView.evaluateJavascript(\n \"window._craftPushReject && window._craftPushReject('Push notifications are disabled')\",\n null\n )\n }`)\n writeFileSync(join(output, 'app/src/main/java', packagePath, 'CraftBridge.kt'), craftBridge)\n const healthTemplate = readFileSync(join(\n TEMPLATES_DIR,\n config.enableHealthConnect ? 'CraftHealthConnect.kt.template' : 'CraftHealthConnectStub.kt.template',\n ), 'utf-8')\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftHealthConnect.kt'),\n healthTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n if (config.enableBackgroundLocation) {\n const serviceTemplate = readFileSync(join(TEMPLATES_DIR, 'LocationRecordingService.kt.template'), 'utf-8')\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'LocationRecordingService.kt'),\n serviceTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n }\n\n // Create AndroidManifest.xml\n const manifestTemplate = readFileSync(join(TEMPLATES_DIR, 'AndroidManifest.xml.template'), 'utf-8')\n const manifest = manifestTemplate\n .replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName)\n .replace(/\\{\\{APP_NAME\\}\\}/g, name)\n .replace(/\\{\\{PERMISSIONS\\}\\}/g, renderAndroidPermissions(config))\n .replace(/\\{\\{USES_CLEARTEXT\\}\\}/g, config.devServerURL?.startsWith('http://') ? 'true' : 'false')\n .replace(/\\{\\{DEEP_LINK_INTENT_FILTERS\\}\\}/g, renderAndroidDeepLinks(config))\n .replace(/\\{\\{BACKGROUND_SERVICE\\}\\}/g, config.enableBackgroundLocation\n ? ' <service android:name=\".LocationRecordingService\" android:exported=\"false\" android:foregroundServiceType=\"location\" android:stopWithTask=\"false\" />'\n : '')\n .replace(/\\{\\{HEALTH_CONNECT_QUERIES\\}\\}/g, config.enableHealthConnect\n ? ' <queries>\\n <package android:name=\"com.google.android.apps.healthdata\" />\\n </queries>'\n : '')\n .replace(/\\{\\{HEALTH_CONNECT_RATIONALE\\}\\}/g, config.enableHealthConnect\n ? ` <intent-filter>\n <action android:name=\"androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE\" />\n </intent-filter>\n <intent-filter>\n <action android:name=\"android.intent.action.VIEW_PERMISSION_USAGE\" />\n <category android:name=\"android.intent.category.HEALTH_PERMISSIONS\" />\n </intent-filter>`\n : '')\n\n writeFileSync(join(output, 'app/src/main/AndroidManifest.xml'), manifest)\n\n // Create build.gradle.kts (project level)\n const projectGradleTemplate = readFileSync(join(TEMPLATES_DIR, 'build.gradle.kts.project.template'), 'utf-8')\n writeFileSync(join(output, 'build.gradle.kts'), projectGradleTemplate\n .replace(/\\{\\{GOOGLE_SERVICES_PLUGIN\\}\\}/g, hasGoogleServices ? ' id(\"com.google.gms.google-services\") version \"4.4.2\" apply false' : ''))\n\n // Create build.gradle.kts (app level)\n const appGradleTemplate = readFileSync(join(TEMPLATES_DIR, 'build.gradle.kts.app.template'), 'utf-8')\n const appGradle = appGradleTemplate\n .replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName)\n .replace(/\\{\\{VERSION_NAME\\}\\}/g, config.version || '1.0.0')\n .replace(/\\{\\{VERSION_CODE\\}\\}/g, String(config.versionCode || 1))\n .replace(/\\{\\{MIN_SDK\\}\\}/g, String(config.minSdk || 24))\n .replace(/\\{\\{COMPILE_SDK\\}\\}/g, String(Math.max(config.compileSdk || 36, config.enableHealthConnect ? 36 : 1)))\n .replace(/\\{\\{TARGET_SDK\\}\\}/g, String(config.targetSdk || 35))\n .replace(/\\{\\{GOOGLE_SERVICES_PLUGIN\\}\\}/g, hasGoogleServices ? ' id(\"com.google.gms.google-services\")' : '')\n .replace(/\\{\\{FIREBASE_MESSAGING_DEPENDENCY\\}\\}/g, config.enablePushNotifications\n ? ' implementation(\"com.google.firebase:firebase-messaging:24.1.0\")'\n : '')\n .replace(/\\{\\{HEALTH_CONNECT_DEPENDENCIES\\}\\}/g, config.enableHealthConnect\n ? ` implementation(\"androidx.health.connect:connect-client:1.1.0\")\n implementation(\"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1\")`\n : '')\n\n writeFileSync(join(output, 'app/build.gradle.kts'), appGradle)\n const proguardTemplate = readFileSync(join(TEMPLATES_DIR, 'proguard-rules.pro.template'), 'utf-8')\n writeFileSync(\n join(output, 'app/proguard-rules.pro'),\n proguardTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n\n // Create settings.gradle.kts\n const settingsTemplate = readFileSync(join(TEMPLATES_DIR, 'settings.gradle.kts.template'), 'utf-8')\n const settings = settingsTemplate.replace(/\\{\\{APP_NAME\\}\\}/g, name)\n writeFileSync(join(output, 'settings.gradle.kts'), settings)\n\n // Create gradle.properties\n const gradleProperties = `org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\nandroid.useAndroidX=true\nkotlin.code.style=official\nandroid.nonTransitiveRClass=true\n`\n writeFileSync(join(output, 'gradle.properties'), gradleProperties)\n\n // Create local.properties placeholder\n writeFileSync(join(output, 'local.properties'), '# SDK location will be set by Android Studio\\n')\n\n // Create gradle wrapper properties\n const gradleWrapperProps = `distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\\\://services.gradle.org/distributions/gradle-8.11.1-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n`\n writeFileSync(join(output, 'gradle/wrapper/gradle-wrapper.properties'), gradleWrapperProps)\n\n // Create res/values files\n const stringsXml = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\">${name}</string>\n</resources>\n`\n writeFileSync(join(output, 'app/src/main/res/values/strings.xml'), stringsXml)\n\n const colorsXml = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"primary\">#1a1a2e</color>\n <color name=\"primary_dark\">#16162b</color>\n <color name=\"accent\">#4ade80</color>\n <color name=\"background\">${config.backgroundColor}</color>\n</resources>\n`\n writeFileSync(join(output, 'app/src/main/res/values/colors.xml'), colorsXml)\n\n const appIconXml = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"108dp\"\n android:height=\"108dp\"\n android:viewportWidth=\"108\"\n android:viewportHeight=\"108\">\n <path android:fillColor=\"${config.backgroundColor}\" android:pathData=\"M0,0h108v108h-108z\" />\n <path android:fillColor=\"#10B981\" android:pathData=\"M18,78L42,42l12,18l12,-12l24,30z\" />\n</vector>\n`\n if (config.appIconPath) {\n if (!existsSync(config.appIconPath)) throw new Error(`App icon not found: ${config.appIconPath}`)\n cpSync(config.appIconPath, join(output, 'app/src/main/res/drawable/craft_app_icon.png'))\n }\n else {\n writeFileSync(join(output, 'app/src/main/res/drawable/craft_app_icon.xml'), appIconXml)\n }\n\n const themesXml = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <style name=\"Theme.CraftApp\" parent=\"Theme.Material3.DayNight.NoActionBar\">\n <item name=\"android:statusBarColor\">@color/primary_dark</item>\n <item name=\"android:navigationBarColor\">@color/primary</item>\n <item name=\"android:windowBackground\">@color/background</item>\n </style>\n</resources>\n`\n writeFileSync(join(output, 'app/src/main/res/values/themes.xml'), themesXml)\n\n // Create activity_main.xml\n const activityMainXml = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"match_parent\"\n android:fitsSystemWindows=\"true\">\n\n <WebView\n android:id=\"@+id/webview\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"match_parent\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n`\n writeFileSync(join(output, 'app/src/main/res/layout/activity_main.xml'), activityMainXml)\n\n // Create placeholder index.html\n const placeholderHtml = `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"viewport-fit=cover, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n <title>${name}</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: 'Roboto', sans-serif;\n background: ${config.backgroundColor};\n color: white;\n min-height: 100vh;\n display: flex;\n justify-content: center;\n align-items: center;\n }\n .container { text-align: center; padding: 2rem; }\n h1 { font-size: 2.5rem; margin-bottom: 1rem; }\n p { opacity: 0.7; }\n .ready { color: #4ade80; font-size: 0.9rem; margin-top: 2rem; }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <h1>⚡ ${name}</h1>\n <p>Built with Craft Android</p>\n <p class=\"ready\" id=\"status\">Waiting for Craft bridge...</p>\n </div>\n <script>\n window.addEventListener('craftReady', (e) => {\n document.getElementById('status').textContent = \\`✓ Craft bridge ready (platform: \\${e.detail.platform})\\`;\n console.log('Craft capabilities:', e.detail.capabilities);\n });\n </script>\n</body>\n</html>`\n\n writeFileSync(join(output, 'app/src/main/assets/index.html'), placeholderHtml)\n\n console.log('✅ Project initialized')\n console.log('')\n console.log('Next steps:')\n console.log(` 1. cd ${output}`)\n console.log(' 2. Add your web content to app/src/main/assets/index.html')\n console.log(' 3. Run: craft android build')\n console.log(' 4. Run: craft android open')\n console.log('')\n}\n\n/**\n * Build Android project\n */\nexport async function build(options: BuildOptions): Promise<void> {\n const { htmlPath, devServer, output, release, compile = true } = options\n\n console.log('\\n📦 Building Craft Android project...')\n\n // Load config\n const configPath = join(output, 'craft.config.json')\n if (!existsSync(configPath)) {\n throw new Error(`No craft.config.json found in ${output}. Run 'craft android init' first.`)\n }\n\n const config: CraftAndroidConfig = JSON.parse(readFileSync(configPath, 'utf-8'))\n\n // Update dev server URL if provided\n if (devServer) {\n config.devServerURL = devServer\n config.trustedOrigins = [...new Set([...(config.trustedOrigins ?? []), new URL(devServer).origin])]\n writeFileSync(configPath, JSON.stringify(config, null, 2))\n writeFileSync(join(output, 'app/src/main/assets/craft.config.json'), JSON.stringify(config, null, 2))\n console.log(` Dev server: ${devServer}`)\n }\n\n // Copy the complete web application if provided.\n if (htmlPath) {\n syncAndroidWebAssets(htmlPath, output)\n config.hasBundledFallback = Boolean(devServer)\n writeFileSync(configPath, JSON.stringify(config, null, 2))\n writeFileSync(join(output, 'app/src/main/assets/craft.config.json'), JSON.stringify(config, null, 2))\n console.log(` Synced: ${htmlPath} → assets/`)\n }\n\n if (!compile) return\n\n // Check if gradlew exists\n const gradlewPath = join(output, 'gradlew')\n if (!existsSync(gradlewPath)) {\n console.log(' Generating Gradle wrapper...')\n try {\n await $`cd ${output} && gradle wrapper`.quiet()\n }\n catch (error) {\n throw new Error('Gradle is unavailable. Install Android Studio or Gradle before building Android.', { cause: error })\n }\n }\n\n // Build the project\n const buildType = release ? 'assembleRelease' : 'assembleDebug'\n console.log(` Building ${release ? 'release' : 'debug'} APK...`)\n\n try {\n await $`cd ${output} && ./gradlew ${buildType}`\n const apkPath = release\n ? 'app/build/outputs/apk/release/app-release.apk'\n : 'app/build/outputs/apk/debug/app-debug.apk'\n console.log(`✅ APK built: ${apkPath}`)\n }\ncatch (error) {\n console.error('Build failed. Open in Android Studio for details.')\n throw error\n }\n\n console.log('')\n}\n\n/**\n * Open Android project in Android Studio\n */\nexport async function open(options: OpenOptions): Promise<void> {\n const { output } = options\n\n if (!existsSync(output)) {\n throw new Error(`No Android project found in ${output}. Run 'craft android init' first.`)\n }\n\n console.log(`🚀 Opening Android project in Android Studio...`)\n\n // Try to open with Android Studio\n try {\n // macOS\n await $`open -a \"Android Studio\" ${output}`.quiet()\n }\ncatch {\n try {\n // Linux\n await $`studio ${output}`.quiet()\n }\ncatch {\n console.log('⚠️ Android Studio not found.')\n console.log(' Please open the project manually in Android Studio:')\n console.log(` ${output}`)\n }\n }\n}\n\n/**\n * Run on Android device or emulator\n */\nexport async function run(options: RunOptions): Promise<void> {\n const { device, output } = options\n\n // First build\n await build({ output })\n\n // Install and run\n console.log('📱 Installing and running on device...')\n\n try {\n const apkPath = join(output, 'app/build/outputs/apk/debug/app-debug.apk')\n\n if (device) {\n await $`adb -s ${device} install -r ${apkPath}`\n }\nelse {\n await $`adb install -r ${apkPath}`\n }\n\n // Get package name from config\n const config: CraftAndroidConfig = JSON.parse(\n readFileSync(join(output, 'craft.config.json'), 'utf-8')\n )\n\n // Launch the app\n const launchCmd = `${config.packageName}/.MainActivity`\n if (device) {\n await $`adb -s ${device} shell am start -n ${launchCmd}`\n }\nelse {\n await $`adb shell am start -n ${launchCmd}`\n }\n\n console.log('✅ App launched')\n }\ncatch (error) {\n console.error('Failed to install/run. Make sure:')\n console.error(' 1. ADB is installed and in PATH')\n console.error(' 2. A device/emulator is connected (run: adb devices)')\n throw error\n }\n}\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "F0D2BDD613AA95E164756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|