@craft-native/android 0.0.224 → 0.0.226
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/README.md +20 -0
- package/dist/index.js +2 -1
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
- package/templates/CraftNativeImageCache.kt.template +142 -0
- package/templates/CraftNativeServices.kt.template +1 -0
- package/templates/CraftNativeShell.kt.template +32 -9
- package/templates/MainActivityNative.kt.template +56 -4
package/README.md
CHANGED
|
@@ -265,6 +265,12 @@ rejected before a project is generated.
|
|
|
265
265
|
before parameters), a tab's path selecting its tab, a path no screen draws
|
|
266
266
|
opening the site in a Custom Tab; deep links and tapped notifications route
|
|
267
267
|
the same way;
|
|
268
|
+
- `craft.navigation.replace('/m/claim?kind=club&slug=rappid')` finishes a flow
|
|
269
|
+
atomically: the host resolves the path and query, closes the current screen,
|
|
270
|
+
and opens or reuses the destination. Android and iOS also keep supporting
|
|
271
|
+
`replace(screenName, params)` for screens without URL routes. A closing isolate
|
|
272
|
+
cannot send a second navigation message, so use this operation when completing
|
|
273
|
+
sign-in instead of sending `popToRoot()` followed by `open(path)`;
|
|
268
274
|
- the app script (`nativeApp`) in its own isolate for the app's life, and in a
|
|
269
275
|
WorkManager job for `backgroundRefresh` and silent pushes
|
|
270
276
|
(`craftBackgroundRefresh`, `craftSilentPush`, answered with
|
|
@@ -272,6 +278,7 @@ rejected before a project is generated.
|
|
|
272
278
|
the app script may open a matched path through `craft.navigation.open`, but
|
|
273
279
|
has no native stack of its own, so `push`, `replace`, `back`, and
|
|
274
280
|
`popToRoot` are no-ops;
|
|
281
|
+
|
|
275
282
|
- `craft.storage.getSync`, `craft.secureStorage.getSync` and `craft.snapshots`:
|
|
276
283
|
JavaScriptSandbox cannot call the host synchronously, so every isolate starts
|
|
277
284
|
with a copy of these stores and the host keeps each copy current. Timers and
|
|
@@ -898,6 +905,19 @@ project is removed after execution. This is also how Craft and consuming apps
|
|
|
898
905
|
execute the native input reconciler regressions, without assuming a particular
|
|
899
906
|
Gradle cache layout.
|
|
900
907
|
|
|
908
|
+
## Native image caching
|
|
909
|
+
|
|
910
|
+
Native `Image` keeps anonymous responses marked `public` with a positive
|
|
911
|
+
`Cache-Control: max-age` in a bounded disk cache, so photos remain available
|
|
912
|
+
after an offline app restart. The cache holds at most 32 MiB of encoded pixels
|
|
913
|
+
and 128 entries, with an 8 MiB limit per image. Freshness and pixels are written
|
|
914
|
+
atomically; failed cache writes do not fail a successful image download.
|
|
915
|
+
|
|
916
|
+
Requests with custom headers, cookie responses, `no-store`, `no-cache`, and
|
|
917
|
+
account-dependent variants are not persisted. Expired entries are fetched
|
|
918
|
+
again. Image pixels honor authored rounded corners without requiring a
|
|
919
|
+
container to hide overflow.
|
|
920
|
+
|
|
901
921
|
## License
|
|
902
922
|
|
|
903
923
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -549,6 +549,7 @@ function writeNativeShellSources(output, packagePath, packageName, config) {
|
|
|
549
549
|
};
|
|
550
550
|
write("CraftBluetooth.kt.template", "CraftBluetooth.kt");
|
|
551
551
|
write("CraftNativeShell.kt.template", "CraftNativeShell.kt");
|
|
552
|
+
write("CraftNativeImageCache.kt.template", "CraftNativeImageCache.kt");
|
|
552
553
|
write("CraftNativeBackground.kt.template", "CraftNativeBackground.kt");
|
|
553
554
|
write(config.enableHealthConnect ? "CraftNativeHealth.kt.template" : "CraftNativeHealthStub.kt.template", "CraftNativeHealth.kt");
|
|
554
555
|
write("CraftNativeServices.kt.template", "CraftNativeServices.kt", (source) => source.replace("{{NATIVE_PUSH_IMPORTS}}", config.enablePushNotifications ? "import com.google.firebase.messaging.FirebaseMessaging" : "").replace("{{NATIVE_PUSH_REGISTER}}", config.enablePushNotifications ? ` try {
|
|
@@ -1080,4 +1081,4 @@ export {
|
|
|
1080
1081
|
ANDROID_MIN_SDK
|
|
1081
1082
|
};
|
|
1082
1083
|
|
|
1083
|
-
//# debugId=
|
|
1084
|
+
//# debugId=E085D9D53817707064756E2164756E21
|
package/dist/index.js.map
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts", "../src/promise-runtime.ts", "../src/testing.ts"],
|
|
4
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, rmSync, statSync, writeFileSync } from 'node:fs'\nimport { dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path'\nimport { $ } from 'bun'\nimport { renderAndroidPromiseRuntime } from './promise-runtime'\n\nexport { runKotlinFixture } from './testing.js'\n\nconst TEMPLATES_DIR = join(dirname(import.meta.dir), 'templates')\n\n/**\n * This builder's version, which the crash reporter names as its SDK version.\n * Walks up from here because a bundled build puts this module below the\n * package root.\n */\nfunction resolveCraftAndroidVersion(startDir: string = import.meta.dir): string {\n let directory = resolve(startDir)\n while (true) {\n try {\n const pkg = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8')) as { name?: unknown, version?: unknown }\n if ((pkg.name === '@craft-native/android' || pkg.name === 'craft-native') && typeof pkg.version === 'string' && pkg.version)\n return pkg.version\n }\n catch {\n // Keep walking.\n }\n const parent = dirname(directory)\n if (parent === directory) return '0.0.0'\n directory = parent\n }\n}\n\nconst CRAFT_ANDROID_VERSION = resolveCraftAndroidVersion()\nconst LOCAL_DEVELOPMENT_HOSTS = new Set(['localhost', '127.0.0.1', '10.0.2.2'])\nconst KOTLIN_KEYWORDS = new Set([\n 'as',\n 'break',\n 'class',\n 'continue',\n 'do',\n 'else',\n 'false',\n 'for',\n 'fun',\n 'if',\n 'in',\n 'interface',\n 'is',\n 'null',\n 'object',\n 'package',\n 'return',\n 'super',\n 'this',\n 'throw',\n 'true',\n 'try',\n 'typealias',\n 'typeof',\n 'val',\n 'var',\n 'when',\n 'while',\n])\n\n/** Oldest Android API level supported by the generated Kotlin runtime. */\nexport const ANDROID_MIN_SDK = 26\n\nexport interface CraftAndroidConfig {\n appName: string\n packageName: string\n renderer?: 'web' | 'native'\n /** \"system\" follows the device; light/dark pin the generated host. */\n appearance?: 'light' | 'dark' | 'system'\n version?: string\n versionCode?: number\n darkMode?: boolean\n backgroundColor?: string\n backgroundColorDark?: 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 enableBluetooth?: boolean\n enableHealthConnect?: boolean\n /**\n * The Health Connect data the app reads and writes, by the shared type\n * names (`steps`, `sleep`, `workouts`...). The manifest declares only\n * these, as Play's health permissions review requires; without it, the\n * original set (steps, heart rate, energy, distance, workouts).\n */\n healthConnect?: {\n read?: HealthConnectType[]\n write?: HealthConnectType[]\n /** Read while the app is in the background (a WorkManager refresh): Android 15's READ_HEALTH_DATA_IN_BACKGROUND. */\n background?: boolean\n /** Read further back than the 30 days before the first grant: READ_HEALTH_DATA_HISTORY. */\n history?: boolean\n }\n enableHealthConnectBackground?: boolean\n enableHealthConnectHistory?: boolean\n /** The app's privacy policy, which Health Connect links to from its permission screen. */\n privacyPolicyURL?: string\n enableKeepAwake?: boolean\n enableDeepLinks?: boolean\n /** Reach the person's own devices on the local network over plain HTTP (a Mac companion). */\n enableLocalNetwork?: boolean\n urlSchemes?: string[]\n /** HTTPS hosts owned by the app; path rules are ordered, with `*` matching any path text. */\n /** Ordered path rules: * matches zero or more characters, ? one, and ?* one or more. */\n appLinks?: Array<{ host: string, paths?: Array<{ pattern: string, exclude?: boolean }> }>\n trustedOrigins?: string[]\n appIconPath?: string\n googleServicesFile?: string\n /** Google Maps Android SDK key used by native `Map` screens. */\n mapsApiKey?: string\n devServerURL?: string\n hasBundledFallback?: boolean\n /** Android API level, at least ANDROID_MIN_SDK (26). */\n minSdk?: number\n compileSdk?: number\n targetSdk?: number\n /**\n * Native renderer: the app's paths and the screen drawing each\n * (`/m/workout/:id` → `Workout`), its tabs, and the bundle route of the\n * script that runs beside the screens. The same keys as the iOS builder.\n */\n nativeScreens?: Record<string, string>\n tabs?: Array<{ id: string, title: string, symbol?: string }>\n nativeApp?: string\n /** Wake the app script every so often (`craftBackgroundRefresh`), through WorkManager. */\n backgroundRefresh?: { enabled?: boolean, minimumIntervalMinutes?: number }\n /**\n * Crash and error reporting to bughq (CraftCrashReporter.kt): uncaught JVM\n * exceptions, ANRs and native crashes (ApplicationExitInfo), uncaught\n * JavaScript errors, and what the page sends through `craft.crashReporter`.\n * Queued on disk and sent when online. Release builds are minified: upload\n * `app/build/outputs/mapping/release/mapping.txt` so bughq can retrace.\n */\n crashReporting?: {\n /** The bughq project's public ingest key. */\n key: string\n /** Default https://bughq.org. */\n host?: string\n /** Default `production`. */\n environment?: string\n /** Default `<versionName>+<versionCode>`, e.g. `1.0.0+41`. */\n release?: string\n /** One session per launch, for crash-free sessions. Default on. */\n sessions?: boolean\n }\n}\n\n/** The shared health data types (the iOS builder's HealthKit names). */\nexport type HealthConnectType = 'steps' | 'heartRate' | 'activeEnergy' | 'distance' | 'workouts' | 'restingHeartRate'\n | 'heartRateVariability' | 'bodyMass' | 'sleep' | 'mindful' | 'dietaryEnergy' | 'dietaryProtein'\n | 'dietaryCarbohydrates' | 'dietaryFat'\n\n/**\n * The Health Connect permissions behind each type, for reading and for\n * writing. CraftNativeHealth.kt asks for the same ones; a permission the\n * manifest does not declare is refused without a prompt.\n */\nexport const HEALTH_CONNECT_PERMISSIONS: Record<HealthConnectType, { read: string[], write: string[] }> = {\n steps: { read: ['READ_STEPS'], write: [] },\n heartRate: { read: ['READ_HEART_RATE'], write: [] },\n activeEnergy: { read: ['READ_ACTIVE_CALORIES_BURNED'], write: ['WRITE_ACTIVE_CALORIES_BURNED'] },\n distance: { read: ['READ_DISTANCE'], write: ['WRITE_DISTANCE'] },\n workouts: {\n read: ['READ_EXERCISE', 'READ_HEART_RATE', 'READ_ACTIVE_CALORIES_BURNED', 'READ_DISTANCE'],\n write: ['WRITE_EXERCISE', 'WRITE_ACTIVE_CALORIES_BURNED', 'WRITE_DISTANCE'],\n },\n restingHeartRate: { read: ['READ_RESTING_HEART_RATE'], write: [] },\n heartRateVariability: { read: ['READ_HEART_RATE_VARIABILITY'], write: [] },\n bodyMass: { read: ['READ_WEIGHT'], write: [] },\n sleep: { read: ['READ_SLEEP'], write: [] },\n mindful: { read: ['READ_MINDFULNESS'], write: ['WRITE_MINDFULNESS'] },\n dietaryEnergy: { read: ['READ_NUTRITION'], write: ['WRITE_NUTRITION'] },\n dietaryProtein: { read: ['READ_NUTRITION'], write: ['WRITE_NUTRITION'] },\n dietaryCarbohydrates: { read: ['READ_NUTRITION'], write: ['WRITE_NUTRITION'] },\n dietaryFat: { read: ['READ_NUTRITION'], write: ['WRITE_NUTRITION'] },\n}\n\nconst DEFAULT_HEALTH_CONNECT = { read: ['steps', 'heartRate', 'activeEnergy', 'distance', 'workouts'], write: ['workouts'] } as const\n\n/** Every `android.permission.health.*` the app's Health Connect types need. */\nexport function healthConnectPermissions(config: Pick<CraftAndroidConfig, 'healthConnect'>): string[] {\n const read = config.healthConnect?.read ?? DEFAULT_HEALTH_CONNECT.read\n const write = config.healthConnect?.write ?? (config.healthConnect ? [] : DEFAULT_HEALTH_CONNECT.write)\n const permissions = new Set<string>()\n for (const type of read) {\n const known = HEALTH_CONNECT_PERMISSIONS[type as HealthConnectType]\n if (!known) throw new Error(`Unknown Health Connect type: ${type}`)\n known.read.forEach(permission => permissions.add(`android.permission.health.${permission}`))\n }\n for (const type of write) {\n const known = HEALTH_CONNECT_PERMISSIONS[type as HealthConnectType]\n if (!known) throw new Error(`Unknown Health Connect type: ${type}`)\n known.write.forEach(permission => permissions.add(`android.permission.health.${permission}`))\n }\n if (config.healthConnect?.background) permissions.add('android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND')\n if (config.healthConnect?.history) permissions.add('android.permission.health.READ_HEALTH_DATA_HISTORY')\n return [...permissions]\n}\n\nexport interface InitOptions {\n name: string\n packageName?: string\n output: string\n config?: Partial<CraftAndroidConfig>\n /**\n * Where to find the Zig runtime's `<abi>/libcraft.so`.\n *\n * `null` means no runtime regardless of the environment; `undefined` falls\n * through to `CRAFT_ANDROID_RUNTIME`. Same shape as the iOS builder's\n * `runtimeDir`.\n */\n runtimeDir?: string | null\n}\n\nexport interface BuildOptions {\n htmlPath?: string\n nativeBundlePath?: string\n /**\n * Native renderer: the app's icons as vectors (`{ viewBox, strokeWidth,\n * icons: { name: svgBody } }`, keyed by Iconify name and SF Symbol), which\n * `<Icon>`, the tab bar and the bar buttons draw. Without it they fall\n * back to the glyphs Android's fonts have.\n */\n iconsPath?: string\n devServer?: string\n output: string\n release?: boolean\n /**\n * An Android App Bundle (`bundleRelease`, `.aab`), what Google Play takes,\n * rather than an APK. Signed with CRAFT_ANDROID_KEYSTORE,\n * CRAFT_ANDROID_KEYSTORE_PASSWORD, CRAFT_ANDROID_KEY_ALIAS and\n * CRAFT_ANDROID_KEY_PASSWORD when they are set.\n */\n bundle?: boolean\n compile?: boolean\n /** Same meaning as `InitOptions.runtimeDir`; refreshes what init installed. */\n runtimeDir?: string | null\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 renderer: 'web',\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 enableBluetooth: false,\n enableHealthConnect: false,\n enableKeepAwake: false,\n enableDeepLinks: false,\n urlSchemes: [],\n appLinks: [],\n trustedOrigins: [],\n minSdk: ANDROID_MIN_SDK,\n compileSdk: 36,\n targetSdk: 35,\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll('\\'', ''')\n}\n\nfunction escapeKotlinString(value: string): string {\n return value\n .replaceAll('\\\\', '\\\\\\\\')\n .replaceAll('\"', '\\\\\"')\n .replaceAll('$', '\\\\$')\n .replaceAll('\\r', '\\\\r')\n .replaceAll('\\n', '\\\\n')\n}\n\nfunction generatedPackageSegment(name: string): string {\n const normalized = name.toLowerCase().replace(/[^a-z0-9_]/g, '')\n if (!normalized) return 'app'\n return /^[a-z_]/.test(normalized) ? normalized : `app${normalized}`\n}\n\nfunction generatedGradleProjectName(name: string): string {\n const invalidCharacters = ['/', '\\\\', ':', '<', '>', '\"', '?', '*', '|']\n const normalized = invalidCharacters.reduce(\n (value, character) => value.replaceAll(character, '-'),\n name,\n ).trim()\n return normalized || 'craft-app'\n}\n\nfunction requireRegularFile(path: string, label: string): void {\n if (!existsSync(path)) throw new Error(`${label} not found: ${path}`)\n if (!statSync(path).isFile()) throw new Error(`${label} must be a file: ${path}`)\n}\n\nfunction containsPath(parent: string, candidate: string): boolean {\n const relativePath = relative(parent, candidate)\n return relativePath === ''\n || (relativePath !== '..' && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath))\n}\n\nfunction validateGoogleServicesFile(path: string, packageName: string): void {\n requireRegularFile(path, 'Google services file')\n\n let document: {\n client?: Array<{\n client_info?: { android_client_info?: { package_name?: string } }\n }>\n }\n try {\n document = JSON.parse(readFileSync(path, 'utf8'))\n }\n catch (error) {\n throw new Error(`Google services file must contain valid JSON: ${path}`, { cause: error })\n }\n\n const matchingClient = document.client?.some((client) => {\n return client.client_info?.android_client_info?.package_name === packageName\n })\n if (!matchingClient) {\n throw new Error(`Google services file has no client for Android package ${packageName}: ${path}`)\n }\n}\n\nfunction androidWebUrl(value: string, field: string): URL {\n let url: URL\n try {\n url = new URL(value)\n }\n catch {\n throw new Error(`Invalid ${field}: ${value}`)\n }\n\n const localDevelopment = url.protocol === 'http:' && LOCAL_DEVELOPMENT_HOSTS.has(url.hostname)\n if ((url.protocol !== 'https:' && !localDevelopment) || url.username || url.password) {\n throw new Error(`${field} must use HTTPS or local HTTP without credentials: ${value}`)\n }\n return url\n}\n\nfunction normalizeAndroidNetworkConfig(config: CraftAndroidConfig): void {\n const schemes = config.urlSchemes?.map(value => value.trim().toLowerCase()) ?? []\n if (schemes.some(value => !/^[a-z][a-z0-9+.-]*$/.test(value))) {\n throw new Error('Android deep-link schemes must be valid URI schemes')\n }\n config.urlSchemes = [...new Set(schemes)]\n config.appLinks = (config.appLinks ?? []).map((link) => {\n const host = link.host.trim().toLowerCase()\n const parsed = new URL(`https://${host}`)\n if (!host || parsed.hostname !== host || parsed.port || parsed.username || parsed.password || parsed.pathname !== '/' || parsed.search || parsed.hash)\n throw new Error('Android App Links need a hostname without a scheme, port or path')\n for (const rule of link.paths ?? []) {\n if (!rule.pattern.startsWith('/') || /[\\x00-\\x1f]/.test(rule.pattern) || (rule.exclude !== undefined && typeof rule.exclude !== 'boolean'))\n throw new Error('Android App Link path rules need an absolute path pattern and a boolean exclude')\n }\n return { ...link, host }\n })\n if (new Set(config.appLinks.map(link => link.host)).size !== config.appLinks.length)\n throw new Error('Android App Link hosts must be unique')\n if (config.enableDeepLinks && config.urlSchemes.length === 0 && config.appLinks.length === 0) {\n throw new Error('Android deep links require at least one URL scheme or an App Link host')\n }\n\n const trustedOrigins = (config.trustedOrigins ?? []).map((value) => {\n return androidWebUrl(value, 'Android trusted origin').origin\n })\n if (config.devServerURL) {\n const devServer = androidWebUrl(config.devServerURL, 'Android dev server URL')\n config.devServerURL = devServer.toString()\n trustedOrigins.push(devServer.origin)\n }\n config.trustedOrigins = [...new Set(trustedOrigins)]\n}\n\nfunction validateAndroidConfig(config: CraftAndroidConfig): void {\n if (!config.appName.trim()) throw new Error('Android app name must not be empty')\n if (config.renderer !== undefined && config.renderer !== 'web' && config.renderer !== 'native') {\n throw new Error(`Unknown Android renderer: ${config.renderer}. Expected web or native.`)\n }\n if (config.appearance !== undefined && !['light', 'dark', 'system'].includes(config.appearance)) {\n throw new Error(`Unknown Android appearance: ${config.appearance}. Expected light, dark, or system.`)\n }\n\n const packageSegments = config.packageName.split('.')\n if (packageSegments.length < 2\n || packageSegments.some(segment => !/^[A-Za-z_][A-Za-z0-9_]*$/.test(segment) || KOTLIN_KEYWORDS.has(segment))) {\n throw new Error(`Invalid Android package name: ${config.packageName}`)\n }\n\n if (!/^#(?:[\\dA-F]{3,4}|[\\dA-F]{6}|[\\dA-F]{8})$/i.test(config.backgroundColor ?? '')) {\n throw new Error(`Invalid Android background color: ${config.backgroundColor}`)\n }\n\n if (config.backgroundColorDark !== undefined && !config.backgroundColorDark.match(/^#[\\dA-F]{3,4}$|^#[\\dA-F]{6}$|^#[\\dA-F]{8}$/i)) {\n throw new Error(`Invalid Android dark background color: ${config.backgroundColorDark}`)\n }\n\n if (config.mapsApiKey !== undefined && !config.mapsApiKey.trim()) {\n throw new Error('Android mapsApiKey must not be empty')\n }\n\n for (const [name, value] of [\n ['versionCode', config.versionCode],\n ['minSdk', config.minSdk],\n ['compileSdk', config.compileSdk],\n ['targetSdk', config.targetSdk],\n ] as const) {\n if (!Number.isInteger(value) || Number(value) < 1) {\n throw new Error(`Android ${name} must be a positive integer`)\n }\n }\n\n if (Number(config.minSdk) < ANDROID_MIN_SDK) {\n throw new Error(`Android minSdk must be at least ${ANDROID_MIN_SDK} (Android 8.0) for the Craft runtime`)\n }\n if (Number(config.minSdk) > Number(config.targetSdk)) {\n throw new Error('Android minSdk must not exceed targetSdk')\n }\n if (Number(config.targetSdk) > Number(config.compileSdk)) {\n throw new Error('Android targetSdk must not exceed compileSdk')\n }\n}\n\nfunction normalizeNativeRoutePath(raw: unknown): string | null {\n if (typeof raw !== 'string') return null\n let path = raw\n const cut = path.search(/[?#]/)\n if (cut >= 0) path = path.slice(0, cut)\n if (!path.startsWith('/')) return null\n path = path.replace(/\\/{2,}/g, '/')\n if (path.length > 1 && path.endsWith('/')) path = path.slice(0, -1)\n return path\n}\n\nfunction nativeRouteSegments(path: string): string[] {\n return path === '/' ? [] : path.slice(1).split('/')\n}\n\nfunction matchesNativeRoute(nativeScreens: Record<string, string> | undefined, raw: string): boolean {\n const path = normalizeNativeRoutePath(raw)\n if (path === null) return false\n const parts = nativeRouteSegments(path)\n return Object.entries(nativeScreens ?? {}).some(([pattern, screen]) => {\n if (!screen) return false\n const normalized = normalizeNativeRoutePath(pattern)\n if (normalized === null) return false\n const route = nativeRouteSegments(normalized)\n for (let index = 0; index < route.length; index++) {\n const segment = route[index]\n if (segment === '*') return index === route.length - 1\n if (index >= parts.length) return false\n if (segment.startsWith(':')) {\n if (!parts[index]) return false\n }\n else if (segment !== parts[index]) return false\n }\n return route.length === parts.length\n })\n}\n\nfunction nativeStartPath(config: CraftAndroidConfig): string {\n if (!config.devServerURL) return '/'\n try {\n const url = new URL(config.devServerURL)\n return `${url.pathname || '/'}${url.search}`\n }\n catch {\n return '/'\n }\n}\n\nfunction validateNativeRouteConfig(config: CraftAndroidConfig): void {\n const screens = Object.entries(config.nativeScreens ?? {})\n for (const [pattern, screen] of screens) {\n if (normalizeNativeRoutePath(pattern) !== pattern || /[?#]/.test(pattern))\n throw new Error(`nativeScreens path \"${pattern}\" must be an absolute path with no query, hash or trailing slash.`)\n if (nativeRouteSegments(normalizeNativeRoutePath(pattern) ?? '/').slice(0, -1).includes('*'))\n throw new Error(`nativeScreens path \"${pattern}\" may only end in *.`)\n if (typeof screen !== 'string' || !/^[A-Za-z_$][\\w$-]*$/.test(screen))\n throw new Error(`nativeScreens screen \"${String(screen)}\" for \"${pattern}\" must be a screen name from the native bundle.`)\n }\n\n const ids = new Set<string>()\n for (const tab of config.tabs ?? []) {\n if (!tab || typeof tab.id !== 'string' || !tab.id || typeof tab.title !== 'string' || !tab.title)\n throw new Error('Each tab needs an id (its root path) and a title.')\n if (ids.has(tab.id)) throw new Error(`Tab \"${tab.id}\" is listed twice.`)\n ids.add(tab.id)\n }\n\n if (config.renderer === 'native' && screens.length > 0) {\n for (const id of ids) {\n if (!matchesNativeRoute(config.nativeScreens, id))\n throw new Error(`Tab \"${id}\" is not a nativeScreens path; with renderer: \"native\" every tab must be a native screen.`)\n }\n const start = nativeStartPath(config)\n if (!matchesNativeRoute(config.nativeScreens, start))\n throw new Error(`The start path \"${start}\" (from devServerURL) is not a nativeScreens path; with renderer: \"native\" the app must start on a native screen.`)\n }\n\n if (config.nativeApp !== undefined) {\n if (typeof config.nativeApp !== 'string' || !/^[A-Za-z_$][\\w$-]*$/.test(config.nativeApp))\n throw new Error(`nativeApp \"${String(config.nativeApp)}\" must be a route name from the native bundle.`)\n if (config.renderer !== 'native' && screens.length === 0)\n throw new Error('nativeApp runs in the native bundle; it needs nativeScreens or renderer: \"native\".')\n }\n}\n\nfunction writeAndroidConfig(output: string, config: CraftAndroidConfig): void {\n writeFileSync(join(output, 'craft.config.json'), JSON.stringify(config, null, 2))\n\n // Generator-only paths can reveal the developer machine's directory layout\n // and have no meaning to the installed app. Keep them in the project config\n // but never package them into the APK's assets.\n const runtimeConfig = { ...config }\n delete runtimeConfig.appIconPath\n delete runtimeConfig.googleServicesFile\n writeFileSync(\n join(output, 'app/src/main/assets/craft.config.json'),\n JSON.stringify(runtimeConfig, null, 2),\n )\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 sourceStat = statSync(sourcePath)\n if (!sourceStat.isDirectory() && !sourceStat.isFile()) {\n throw new Error(`Web asset path must be a file or directory: ${source}`)\n }\n\n const assetsDir = resolve(output, 'app/src/main/assets')\n if (containsPath(sourcePath, assetsDir) || containsPath(assetsDir, sourcePath)) {\n throw new Error(`Web asset source must not overlap generated asset directory: ${source}`)\n }\n if (sourceStat.isDirectory()) {\n requireRegularFile(join(sourcePath, 'index.html'), 'Web asset directory entry point')\n }\n\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 (sourceStat.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) healthConnectPermissions(config).forEach(permission => permissions.add(permission))\n // A background refresh (WorkManager) after a restart.\n if (config.renderer === 'native' && config.backgroundRefresh?.enabled) permissions.add('android.permission.RECEIVE_BOOT_COMPLETED')\n const bluetooth = config.enableBluetooth ? [\n ' <uses-permission android:name=\"android.permission.BLUETOOTH\" android:maxSdkVersion=\"30\" />',\n ' <uses-permission android:name=\"android.permission.BLUETOOTH_ADMIN\" android:maxSdkVersion=\"30\" />',\n ' <uses-permission android:name=\"android.permission.BLUETOOTH_SCAN\" android:usesPermissionFlags=\"neverForLocation\" />',\n ' <uses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\" />',\n ...(config.enableGeolocation ? [] : [' <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" android:maxSdkVersion=\"30\" />']),\n ].join('\\n') : ''\n return [bluetooth, ...[...permissions].map(permission => ` <uses-permission android:name=\"${permission}\" />`)].filter(Boolean).join('\\n')\n}\n\n/**\n * The native renderer's own sources: the app around the screens (routes,\n * stores, bars), its services (dialogs, browser, share, push, Health\n * Connect), background refresh, and Firebase messaging when push is on.\n */\nfunction writeNativeShellSources(output: string, packagePath: string, packageName: string, config: CraftAndroidConfig): void {\n const javaDir = join(output, 'app/src/main/java', packagePath)\n const write = (template: string, file: string, replace: (source: string) => string = source => source): void => {\n const source = readFileSync(join(TEMPLATES_DIR, template), 'utf-8').replace(/\\{\\{PACKAGE_NAME\\}\\}/g, packageName)\n writeFileSync(join(javaDir, file), replace(source))\n }\n write('CraftBluetooth.kt.template', 'CraftBluetooth.kt')\n write('CraftNativeShell.kt.template', 'CraftNativeShell.kt')\n write('CraftNativeBackground.kt.template', 'CraftNativeBackground.kt')\n write(config.enableHealthConnect ? 'CraftNativeHealth.kt.template' : 'CraftNativeHealthStub.kt.template', 'CraftNativeHealth.kt')\n write('CraftNativeServices.kt.template', 'CraftNativeServices.kt', source => source\n .replace('{{NATIVE_PUSH_IMPORTS}}', config.enablePushNotifications ? 'import com.google.firebase.messaging.FirebaseMessaging' : '')\n .replace('{{NATIVE_PUSH_REGISTER}}', config.enablePushNotifications\n ? ` try {\n FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->\n val token = if (task.isSuccessful) task.result else null\n if (token.isNullOrBlank()) done(failure(\"PUSH_UNAVAILABLE\", task.exception?.message ?: \"Firebase Cloud Messaging gave no token\"))\n else {\n context.getSharedPreferences(\"craft-native-push\", Context.MODE_PRIVATE).edit().putString(\"token\", token).apply()\n done(success(token))\n }\n }\n } catch (error: Exception) {\n done(failure(\"PUSH_UNAVAILABLE\", error.message ?: \"Firebase Cloud Messaging is not configured\"))\n }`\n : ' done(failure(\"CAPABILITY_DISABLED\", \"Push notifications are disabled\"))'))\n if (config.enablePushNotifications) write('CraftNativeMessagingService.kt.template', 'CraftNativeMessagingService.kt')\n else rmSync(join(javaDir, 'CraftNativeMessagingService.kt'), { force: true })\n // Files a screen shares (craft.files' copies, in the cache) go to other apps through this provider.\n mkdirSync(join(output, 'app/src/main/res/xml'), { recursive: true })\n writeFileSync(join(output, 'app/src/main/res/xml/craft_file_paths.xml'), `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n <cache-path name=\"craft-files\" path=\"craft-files/\" />\n <files-path name=\"craft-media\" path=\"craft-media/\" />\n</paths>\n`)\n}\n\n/** What the native renderer adds to the manifest's application element. */\nfunction renderNativeShellManifest(config: CraftAndroidConfig): string {\n const parts = [\n ' <receiver android:name=\".CraftNativeNotificationReceiver\" android:exported=\"false\" />',\n ` <provider\n android:name=\"androidx.core.content.FileProvider\"\n android:authorities=\"${config.packageName}.craftfiles\"\n android:exported=\"false\"\n android:grantUriPermissions=\"true\">\n <meta-data android:name=\"android.support.FILE_PROVIDER_PATHS\" android:resource=\"@xml/craft_file_paths\" />\n </provider>`,\n ]\n if (config.enablePushNotifications) {\n parts.push(` <service android:name=\".CraftNativeMessagingService\" android:exported=\"false\">\n <intent-filter>\n <action android:name=\"com.google.firebase.MESSAGING_EVENT\" />\n </intent-filter>\n </service>`)\n }\n if (config.enableHealthConnect) {\n // Android 14 and later open the app's privacy policy from Health\n // Connect's permission screen through this alias.\n parts.push(` <activity-alias\n android:name=\"ViewPermissionUsageActivity\"\n android:exported=\"true\"\n android:targetActivity=\".MainActivity\"\n android:permission=\"android.permission.START_VIEW_PERMISSION_USAGE\">\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 </activity-alias>`)\n }\n return parts.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 const custom = 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 const verified = (config.appLinks ?? []).map(link => {\n if (!/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/i.test(link.host)) throw new Error('Invalid Android App Link host')\n return ` <intent-filter android:autoVerify=\"true\">\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=\"https\" android:host=\"${link.host}\" />\n </intent-filter>`\n }).join('\\n')\n return [custom, verified].filter(Boolean).join('\\n')\n}\n\n/**\n * Initialize a new Android project\n */\n/**\n * The ABI directories a runtime directory is expected to hold.\n *\n * These are Android's own names for the two architectures craft builds, and\n * they are also `zig build build-android-all`'s output layout — `zig-out/android/`\n * already contains `arm64-v8a/libcraft.so` and `x86_64/libcraft.so`, which is\n * exactly the shape `app/src/main/jniLibs/` wants. So the install below is a\n * copy and not a rearrangement.\n *\n * x86_64 is not optional-in-practice: every Android emulator anyone tests on\n * is x86_64, so a runtime shipped for arm64 alone is one that never runs in\n * CI and never runs on a developer's machine either.\n */\nconst RUNTIME_ABIS = ['arm64-v8a', 'x86_64'] as const\n\n/**\n * Where the runtime directory comes from when the caller does not say.\n *\n * The same shape as `CRAFT_BIN` and the iOS builder's `CRAFT_IOS_RUNTIME`: an\n * explicit override for the monorepo dev loop, not a lookup path. Shipping the\n * runtime to real apps means putting these libraries in the pantry package\n * beside the `craft` binary, which is a distribution decision this function\n * does not make.\n */\nexport function resolveRuntimeDir(override?: string | null): string | null {\n if (override === null) return null\n const dir = override ?? process.env.CRAFT_ANDROID_RUNTIME\n if (!dir) return null\n if (!existsSync(dir)) {\n const source = override === undefined ? 'CRAFT_ANDROID_RUNTIME points at' : 'runtimeDir is'\n throw new Error(`${source} ${dir}, which does not exist.`)\n }\n return dir\n}\n\n/**\n * Copy the Zig runtime into the generated project as\n * `app/src/main/jniLibs/<abi>/libcraft.so`.\n *\n * That path is AGP's default `jniLibs.srcDirs`, so nothing in the Gradle\n * templates has to know about it — the library is packaged into the APK and\n * `System.loadLibrary(\"craft\")` finds it because the file is named for the\n * `craft` it asks for.\n *\n * Until this existed, nothing put the library anywhere. `CraftNative` caught\n * the `UnsatisfiedLinkError`, set `isAvailable = false`, and every action fell\n * through to the Kotlin shim — by design, so an app with no runtime still\n * works, which is also why nobody noticed that *every* generated app was in\n * that state and the whole Android half of the Zig bridge had never run.\n *\n * Returns true when a runtime was installed.\n */\nexport function installRuntime(output: string, runtimeDir: string): boolean {\n // Every ABI resolved before anything is written, so a half-populated\n // runtime directory cannot leave a project carrying one architecture and\n // claiming both. A single-ABI APK installs fine and then fails to load on\n // the other kind of device, which is the break that only shows up on\n // somebody else's phone.\n const found = RUNTIME_ABIS.map(abi => ({ abi, source: join(runtimeDir, abi, 'libcraft.so') }))\n .filter(entry => existsSync(entry.source))\n\n if (found.length === 0) {\n throw new Error(\n `${runtimeDir} has no <abi>/libcraft.so for any of ${RUNTIME_ABIS.join(', ')}. `\n + 'Run `zig build build-android-all -Doptimize=ReleaseSafe` in packages/zig and point at its zig-out/android.',\n )\n }\n\n if (found.length < RUNTIME_ABIS.length) {\n const missing = RUNTIME_ABIS.filter(abi => !found.some(entry => entry.abi === abi))\n console.warn(\n ` ⚠ only ${found.map(entry => entry.abi).join(', ')} was found; ${missing.join(', ')} is missing. `\n + 'The app will fall back to the Kotlin shim on those devices.',\n )\n }\n\n const dest = join(output, 'app/src/main/jniLibs')\n // Wiped rather than merged, so an ABI dropped from a later build does not\n // linger in the project as a stale library the APK still ships.\n rmSync(dest, { force: true, recursive: true })\n\n for (const { abi, source } of found) {\n const abiDir = join(dest, abi)\n mkdirSync(abiDir, { recursive: true })\n cpSync(source, join(abiDir, 'libcraft.so'))\n }\n\n return true\n}\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.${generatedPackageSegment(name)}`\n const packagePath = finalPackageName.replace(/\\./g, '/')\n\n // Command-level identity always wins over duplicate fields in `config`.\n // Normalize implied capabilities before validation so craft.config.json and\n // the generated Gradle source describe the same effective project.\n // Undefined keeps the default instead of erasing it (see the iOS builder).\n const given = Object.fromEntries(Object.entries(options.config ?? {}).filter(([, value]) => value !== undefined))\n const config: CraftAndroidConfig = {\n ...DEFAULT_CONFIG,\n ...given,\n appName: name,\n packageName: finalPackageName,\n }\n if (config.enableBackgroundLocation) config.enableGeolocation = true\n if (config.enableHealthConnect) {\n config.compileSdk = Math.max(config.compileSdk ?? 36, 36)\n config.enableHealthConnectBackground = Boolean(config.healthConnect?.background)\n config.enableHealthConnectHistory = Boolean(config.healthConnect?.history)\n healthConnectPermissions(config)\n }\n normalizeAndroidNetworkConfig(config)\n validateAndroidConfig(config)\n validateNativeRouteConfig(config)\n if (config.enablePushNotifications && !config.googleServicesFile) {\n throw new Error('Android push notifications require a googleServicesFile')\n }\n if (config.googleServicesFile) validateGoogleServicesFile(config.googleServicesFile, finalPackageName)\n if (config.appIconPath) requireRegularFile(config.appIconPath, 'App icon')\n const appIconExtension = config.appIconPath ? extname(config.appIconPath).toLowerCase() : undefined\n if (config.appIconPath && !['.gif', '.jpg', '.png', '.webp'].includes(appIconExtension ?? '')) {\n throw new Error(`Unsupported Android app icon format: ${appIconExtension || '(none)'}`)\n }\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/values-night'),\n join(output, 'app/src/main/res/drawable'),\n join(output, 'app/src/main/assets'),\n join(output, 'gradle/wrapper'),\n ]\n\n if (existsSync(output) && !statSync(output).isDirectory()) {\n throw new Error(`Android project output must be a directory: ${output}`)\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 writeAndroidConfig(output, config)\n\n const hasGoogleServices = Boolean(config.googleServicesFile)\n if (config.googleServicesFile) {\n cpSync(config.googleServicesFile, join(output, 'app/google-services.json'))\n }\n\n // Copy templates\n const mainActivityTemplate = readFileSync(join(\n TEMPLATES_DIR,\n config.renderer === 'native' ? 'MainActivityNative.kt.template' : 'MainActivity.kt.template',\n ), '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 writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftAppLinks.kt'),\n readFileSync(join(TEMPLATES_DIR, 'CraftAppLinks.kt.template'), 'utf8').replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n // Both renderers: crash reporting does not depend on how screens are drawn.\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftCrashReporter.kt'),\n readFileSync(join(TEMPLATES_DIR, 'CraftCrashReporter.kt.template'), 'utf-8')\n .replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName)\n .replace(/\\{\\{CRAFT_VERSION\\}\\}/g, CRAFT_ANDROID_VERSION),\n )\n if (config.renderer === 'native') {\n const mutationTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeMutation.kt.template'), 'utf-8')\n const flatListTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeFlatList.kt.template'), 'utf-8')\n const refreshableFlatListTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeRefreshableFlatList.kt.template'), 'utf-8')\n const capabilityTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeCapabilities.kt.template'), 'utf-8')\n const notificationTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeNotificationReceiver.kt.template'), 'utf-8')\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeMutation.kt'),\n mutationTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeFlatList.kt'),\n flatListTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeRefreshableFlatList.kt'),\n refreshableFlatListTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeCapabilities.kt'),\n capabilityTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeNotificationReceiver.kt'),\n notificationTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeNativeShellSources(output, packagePath, finalPackageName, config)\n }\n\n // Create CraftBridge.kt\n const craftBridgeTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftBridge.kt.template'), 'utf-8')\n const craftBridge = craftBridgeTemplate\n .replace(/\\{\\{PROMISE_RUNTIME\\}\\}/g, () => renderAndroidPromiseRuntime(' '))\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 evaluatePromiseJavascript(\"$callback && $callback($payload)\")\n }\n } catch (error: Exception) {\n val payload = JSONObject.quote(error.message ?: \"Firebase Cloud Messaging is not configured\")\n evaluatePromiseJavascript(\"window._craftPushReject && window._craftPushReject($payload)\")\n }\n }`\n : `evaluatePromiseJavascript(\n \"window._craftPushReject && window._craftPushReject('Push notifications are disabled')\"\n )`)\n writeFileSync(join(output, 'app/src/main/java', packagePath, 'CraftBridge.kt'), craftBridge)\n\n // CraftNative goes to a fixed package, not the app's. The prebuilt\n // libcraft.so binds its natives by class name in JNI_OnLoad, and it cannot\n // know a name chosen here — so this one file is deliberately not templated\n // and deliberately not under packagePath.\n const craftNative = readFileSync(join(TEMPLATES_DIR, 'CraftNative.kt.template'), 'utf-8')\n const nativeDir = join(output, 'app/src/main/java/com/craft/runtime')\n mkdirSync(nativeDir, { recursive: true })\n writeFileSync(join(nativeDir, 'CraftNative.kt'), craftNative)\n // The bridge and native holder both reference the recording store/service,\n // even when the service is not registered in the manifest. Keep these\n // support types in the same fixed package as CraftNative so every generated\n // app compiles regardless of its chosen package name or feature flags.\n const serviceTemplate = readFileSync(join(TEMPLATES_DIR, 'LocationRecordingService.kt.template'), 'utf-8')\n writeFileSync(join(nativeDir, 'LocationRecordingService.kt'), serviceTemplate)\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 // 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 // Plain HTTP only for a local dev server, or an app that talks to the\n // person's own devices on the LAN (which have no public certificate).\n .replace(/\\{\\{USES_CLEARTEXT\\}\\}/g, config.devServerURL?.startsWith('http://') || config.enableLocalNetwork ? 'true' : 'false')\n .replace(/\\{\\{DEEP_LINK_INTENT_FILTERS\\}\\}/g, renderAndroidDeepLinks(config))\n .replace(/\\{\\{BACKGROUND_SERVICE\\}\\}/g, config.enableBackgroundLocation\n ? ' <service android:name=\"com.craft.runtime.LocationRecordingService\" android:exported=\"false\" android:foregroundServiceType=\"location\" android:stopWithTask=\"false\" />'\n : '')\n .replace(/\\{\\{NATIVE_NOTIFICATION_RECEIVER\\}\\}/g, config.renderer === 'native'\n ? renderNativeShellManifest(config)\n : '')\n // Starts the crash reporter before Application.onCreate, for every way\n // the process starts (CraftCrashReporter.kt). Inert without a key.\n .replace(/\\{\\{CRASH_REPORTER_PROVIDER\\}\\}/g, ` <provider android:name=\".CraftCrashReporterInit\" android:authorities=\"\\${applicationId}.craft-crash\" android:exported=\"false\" android:initOrder=\"1000\" />`)\n .replace(/\\{\\{GOOGLE_MAPS_API_KEY\\}\\}/g, config.mapsApiKey\n ? ` <meta-data android:name=\"com.google.android.geo.API_KEY\" android:value=\"${escapeXml(config.mapsApiKey)}\" />`\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>${config.renderer === 'native' ? '' : `\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, escapeKotlinString(config.version || '1.0.0'))\n .replace(/\\{\\{VERSION_CODE\\}\\}/g, String(config.versionCode || 1))\n .replace(/\\{\\{MIN_SDK\\}\\}/g, String(config.minSdk ?? ANDROID_MIN_SDK))\n .replace(/\\{\\{COMPILE_SDK\\}\\}/g, String(config.compileSdk || 36))\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(/\\{\\{REFRESH_LAYOUT_DEPENDENCY\\}\\}/g, config.renderer === 'native'\n ? ' implementation(\"androidx.swiperefreshlayout:swiperefreshlayout:1.1.0\")'\n : '')\n .replace(/\\{\\{GOOGLE_MAPS_DEPENDENCY\\}\\}/g, config.renderer === 'native'\n ? ' // Native STX Map overlays and trail routes\\n implementation(\"com.google.android.gms:play-services-maps:18.2.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 .replace(/\\{\\{JAVASCRIPT_ENGINE_DEPENDENCY\\}\\}/g, config.renderer === 'native'\n ? ` implementation(\"androidx.javascriptengine:javascriptengine:1.1.0\")\n // Native background refresh (WorkManager).\n implementation(\"androidx.work:work-runtime-ktx:2.9.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(\n /\\{\\{APP_NAME\\}\\}/g,\n escapeKotlinString(generatedGradleProjectName(name)),\n )\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\">${escapeXml(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 const darkColorsPath = join(output, 'app/src/main/res/values-night/colors.xml')\n if (config.backgroundColorDark) {\n writeFileSync(darkColorsPath, `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"background\">${config.backgroundColorDark}</color>\n</resources>\n`)\n }\n else {\n rmSync(darkColorsPath, { force: true })\n }\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 cpSync(\n config.appIconPath,\n join(output, `app/src/main/res/drawable/craft_app_icon${appIconExtension}`),\n )\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 = config.renderer === 'native'\n ? `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:id=\"@+id/native_root\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"match_parent\" />\n`\n : `<?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>${escapeXml(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>⚡ ${escapeXml(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 // The Zig runtime, when one was configured. Absent, the generated app is the\n // Kotlin shim and behaves exactly as it always has — which is the fallback\n // `CraftNative` was written for, not a failure.\n const runtimeDir = resolveRuntimeDir(options.runtimeDir)\n if (runtimeDir) {\n installRuntime(output, runtimeDir)\n console.log(' Installed the Zig runtime from', runtimeDir)\n }\n\n console.log('✅ Project initialized')\n console.log('')\n console.log('Next steps:')\n console.log(` 1. cd ${output}`)\n if (config.renderer === 'native') {\n console.log(' 2. Compile your screen: stx native compile Screen.stx --format bundle --output screen.js')\n console.log(' 3. Run: craft android build --native-bundle screen.js')\n }\n else {\n console.log(' 2. Add your web content to app/src/main/assets/index.html')\n console.log(' 3. Run: craft android build')\n }\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, nativeBundlePath, devServer, output, release, bundle, 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 validateAndroidConfig(config)\n validateNativeRouteConfig(config)\n\n if (config.renderer === 'native') {\n if (htmlPath || devServer) throw new Error('A native Android screen cannot use --html-path or --dev-server.')\n const destination = join(output, 'app/src/main/assets/native-screen.js')\n if (nativeBundlePath && resolve(nativeBundlePath) !== resolve(destination)) cpSync(nativeBundlePath, destination)\n const icons = join(output, 'app/src/main/assets/craft-icons.json')\n if (options.iconsPath) {\n requireRegularFile(options.iconsPath, 'Icon table')\n if (resolve(options.iconsPath) !== resolve(icons)) cpSync(options.iconsPath, icons)\n }\n if (!existsSync(destination)) {\n throw new Error('Native Android mode needs app/src/main/assets/native-screen.js. Compile a .stx screen with `stx native compile` first.')\n }\n }\n else if (nativeBundlePath) {\n throw new Error('--native-bundle requires renderer: \"native\" in craft.config.json.')\n }\n\n // Refresh the runtime the same way the iOS builder does: only for a project\n // that already has one, so `craft android build` in a shim-only project does\n // not quietly acquire a native library it was never generated with.\n if (existsSync(join(output, 'app/src/main/jniLibs'))) {\n const runtimeDir = resolveRuntimeDir(options.runtimeDir)\n if (runtimeDir) {\n installRuntime(output, runtimeDir)\n console.log(' Refreshed the Zig runtime from', runtimeDir)\n }\n else {\n console.log(' Keeping the Zig runtime installed at init (no runtime directory configured)')\n }\n }\n\n // Update dev server URL if provided\n if (devServer) {\n const url = androidWebUrl(devServer, 'Android dev server URL')\n config.devServerURL = url.toString()\n config.trustedOrigins = [...new Set([\n ...(config.trustedOrigins ?? []).map(value => androidWebUrl(value, 'Android trusted origin').origin),\n url.origin,\n ])]\n writeAndroidConfig(output, config)\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 writeAndroidConfig(output, config)\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 variant = release ? 'Release' : 'Debug'\n const buildType = `${bundle ? 'bundle' : 'assemble'}${variant}`\n console.log(` Building ${release ? 'release' : 'debug'} ${bundle ? 'app bundle' : 'APK'}...`)\n if (release && !process.env.CRAFT_ANDROID_KEYSTORE)\n console.log(' CRAFT_ANDROID_KEYSTORE is not set: the release build is unsigned.')\n\n try {\n await $`cd ${output} && ./gradlew ${buildType}`\n const artifact = bundle\n ? `app/build/outputs/bundle/${variant.toLowerCase()}/app-${variant.toLowerCase()}.aab`\n : release\n ? 'app/build/outputs/apk/release/app-release.apk'\n : 'app/build/outputs/apk/debug/app-debug.apk'\n console.log(`✅ ${bundle ? 'App bundle' : 'APK'} built: ${artifact}`)\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",
|
|
5
|
+
"/**\n * Craft Android Builder\n *\n * Generates native Android apps from web content using WebView.\n */\n\nimport { cpSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'\nimport { dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path'\nimport { $ } from 'bun'\nimport { renderAndroidPromiseRuntime } from './promise-runtime'\n\nexport { runKotlinFixture } from './testing.js'\n\nconst TEMPLATES_DIR = join(dirname(import.meta.dir), 'templates')\n\n/**\n * This builder's version, which the crash reporter names as its SDK version.\n * Walks up from here because a bundled build puts this module below the\n * package root.\n */\nfunction resolveCraftAndroidVersion(startDir: string = import.meta.dir): string {\n let directory = resolve(startDir)\n while (true) {\n try {\n const pkg = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8')) as { name?: unknown, version?: unknown }\n if ((pkg.name === '@craft-native/android' || pkg.name === 'craft-native') && typeof pkg.version === 'string' && pkg.version)\n return pkg.version\n }\n catch {\n // Keep walking.\n }\n const parent = dirname(directory)\n if (parent === directory) return '0.0.0'\n directory = parent\n }\n}\n\nconst CRAFT_ANDROID_VERSION = resolveCraftAndroidVersion()\nconst LOCAL_DEVELOPMENT_HOSTS = new Set(['localhost', '127.0.0.1', '10.0.2.2'])\nconst KOTLIN_KEYWORDS = new Set([\n 'as',\n 'break',\n 'class',\n 'continue',\n 'do',\n 'else',\n 'false',\n 'for',\n 'fun',\n 'if',\n 'in',\n 'interface',\n 'is',\n 'null',\n 'object',\n 'package',\n 'return',\n 'super',\n 'this',\n 'throw',\n 'true',\n 'try',\n 'typealias',\n 'typeof',\n 'val',\n 'var',\n 'when',\n 'while',\n])\n\n/** Oldest Android API level supported by the generated Kotlin runtime. */\nexport const ANDROID_MIN_SDK = 26\n\nexport interface CraftAndroidConfig {\n appName: string\n packageName: string\n renderer?: 'web' | 'native'\n /** \"system\" follows the device; light/dark pin the generated host. */\n appearance?: 'light' | 'dark' | 'system'\n version?: string\n versionCode?: number\n darkMode?: boolean\n backgroundColor?: string\n backgroundColorDark?: 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 enableBluetooth?: boolean\n enableHealthConnect?: boolean\n /**\n * The Health Connect data the app reads and writes, by the shared type\n * names (`steps`, `sleep`, `workouts`...). The manifest declares only\n * these, as Play's health permissions review requires; without it, the\n * original set (steps, heart rate, energy, distance, workouts).\n */\n healthConnect?: {\n read?: HealthConnectType[]\n write?: HealthConnectType[]\n /** Read while the app is in the background (a WorkManager refresh): Android 15's READ_HEALTH_DATA_IN_BACKGROUND. */\n background?: boolean\n /** Read further back than the 30 days before the first grant: READ_HEALTH_DATA_HISTORY. */\n history?: boolean\n }\n enableHealthConnectBackground?: boolean\n enableHealthConnectHistory?: boolean\n /** The app's privacy policy, which Health Connect links to from its permission screen. */\n privacyPolicyURL?: string\n enableKeepAwake?: boolean\n enableDeepLinks?: boolean\n /** Reach the person's own devices on the local network over plain HTTP (a Mac companion). */\n enableLocalNetwork?: boolean\n urlSchemes?: string[]\n /** HTTPS hosts owned by the app; path rules are ordered, with `*` matching any path text. */\n /** Ordered path rules: * matches zero or more characters, ? one, and ?* one or more. */\n appLinks?: Array<{ host: string, paths?: Array<{ pattern: string, exclude?: boolean }> }>\n trustedOrigins?: string[]\n appIconPath?: string\n googleServicesFile?: string\n /** Google Maps Android SDK key used by native `Map` screens. */\n mapsApiKey?: string\n devServerURL?: string\n hasBundledFallback?: boolean\n /** Android API level, at least ANDROID_MIN_SDK (26). */\n minSdk?: number\n compileSdk?: number\n targetSdk?: number\n /**\n * Native renderer: the app's paths and the screen drawing each\n * (`/m/workout/:id` → `Workout`), its tabs, and the bundle route of the\n * script that runs beside the screens. The same keys as the iOS builder.\n */\n nativeScreens?: Record<string, string>\n tabs?: Array<{ id: string, title: string, symbol?: string }>\n nativeApp?: string\n /** Wake the app script every so often (`craftBackgroundRefresh`), through WorkManager. */\n backgroundRefresh?: { enabled?: boolean, minimumIntervalMinutes?: number }\n /**\n * Crash and error reporting to bughq (CraftCrashReporter.kt): uncaught JVM\n * exceptions, ANRs and native crashes (ApplicationExitInfo), uncaught\n * JavaScript errors, and what the page sends through `craft.crashReporter`.\n * Queued on disk and sent when online. Release builds are minified: upload\n * `app/build/outputs/mapping/release/mapping.txt` so bughq can retrace.\n */\n crashReporting?: {\n /** The bughq project's public ingest key. */\n key: string\n /** Default https://bughq.org. */\n host?: string\n /** Default `production`. */\n environment?: string\n /** Default `<versionName>+<versionCode>`, e.g. `1.0.0+41`. */\n release?: string\n /** One session per launch, for crash-free sessions. Default on. */\n sessions?: boolean\n }\n}\n\n/** The shared health data types (the iOS builder's HealthKit names). */\nexport type HealthConnectType = 'steps' | 'heartRate' | 'activeEnergy' | 'distance' | 'workouts' | 'restingHeartRate'\n | 'heartRateVariability' | 'bodyMass' | 'sleep' | 'mindful' | 'dietaryEnergy' | 'dietaryProtein'\n | 'dietaryCarbohydrates' | 'dietaryFat'\n\n/**\n * The Health Connect permissions behind each type, for reading and for\n * writing. CraftNativeHealth.kt asks for the same ones; a permission the\n * manifest does not declare is refused without a prompt.\n */\nexport const HEALTH_CONNECT_PERMISSIONS: Record<HealthConnectType, { read: string[], write: string[] }> = {\n steps: { read: ['READ_STEPS'], write: [] },\n heartRate: { read: ['READ_HEART_RATE'], write: [] },\n activeEnergy: { read: ['READ_ACTIVE_CALORIES_BURNED'], write: ['WRITE_ACTIVE_CALORIES_BURNED'] },\n distance: { read: ['READ_DISTANCE'], write: ['WRITE_DISTANCE'] },\n workouts: {\n read: ['READ_EXERCISE', 'READ_HEART_RATE', 'READ_ACTIVE_CALORIES_BURNED', 'READ_DISTANCE'],\n write: ['WRITE_EXERCISE', 'WRITE_ACTIVE_CALORIES_BURNED', 'WRITE_DISTANCE'],\n },\n restingHeartRate: { read: ['READ_RESTING_HEART_RATE'], write: [] },\n heartRateVariability: { read: ['READ_HEART_RATE_VARIABILITY'], write: [] },\n bodyMass: { read: ['READ_WEIGHT'], write: [] },\n sleep: { read: ['READ_SLEEP'], write: [] },\n mindful: { read: ['READ_MINDFULNESS'], write: ['WRITE_MINDFULNESS'] },\n dietaryEnergy: { read: ['READ_NUTRITION'], write: ['WRITE_NUTRITION'] },\n dietaryProtein: { read: ['READ_NUTRITION'], write: ['WRITE_NUTRITION'] },\n dietaryCarbohydrates: { read: ['READ_NUTRITION'], write: ['WRITE_NUTRITION'] },\n dietaryFat: { read: ['READ_NUTRITION'], write: ['WRITE_NUTRITION'] },\n}\n\nconst DEFAULT_HEALTH_CONNECT = { read: ['steps', 'heartRate', 'activeEnergy', 'distance', 'workouts'], write: ['workouts'] } as const\n\n/** Every `android.permission.health.*` the app's Health Connect types need. */\nexport function healthConnectPermissions(config: Pick<CraftAndroidConfig, 'healthConnect'>): string[] {\n const read = config.healthConnect?.read ?? DEFAULT_HEALTH_CONNECT.read\n const write = config.healthConnect?.write ?? (config.healthConnect ? [] : DEFAULT_HEALTH_CONNECT.write)\n const permissions = new Set<string>()\n for (const type of read) {\n const known = HEALTH_CONNECT_PERMISSIONS[type as HealthConnectType]\n if (!known) throw new Error(`Unknown Health Connect type: ${type}`)\n known.read.forEach(permission => permissions.add(`android.permission.health.${permission}`))\n }\n for (const type of write) {\n const known = HEALTH_CONNECT_PERMISSIONS[type as HealthConnectType]\n if (!known) throw new Error(`Unknown Health Connect type: ${type}`)\n known.write.forEach(permission => permissions.add(`android.permission.health.${permission}`))\n }\n if (config.healthConnect?.background) permissions.add('android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND')\n if (config.healthConnect?.history) permissions.add('android.permission.health.READ_HEALTH_DATA_HISTORY')\n return [...permissions]\n}\n\nexport interface InitOptions {\n name: string\n packageName?: string\n output: string\n config?: Partial<CraftAndroidConfig>\n /**\n * Where to find the Zig runtime's `<abi>/libcraft.so`.\n *\n * `null` means no runtime regardless of the environment; `undefined` falls\n * through to `CRAFT_ANDROID_RUNTIME`. Same shape as the iOS builder's\n * `runtimeDir`.\n */\n runtimeDir?: string | null\n}\n\nexport interface BuildOptions {\n htmlPath?: string\n nativeBundlePath?: string\n /**\n * Native renderer: the app's icons as vectors (`{ viewBox, strokeWidth,\n * icons: { name: svgBody } }`, keyed by Iconify name and SF Symbol), which\n * `<Icon>`, the tab bar and the bar buttons draw. Without it they fall\n * back to the glyphs Android's fonts have.\n */\n iconsPath?: string\n devServer?: string\n output: string\n release?: boolean\n /**\n * An Android App Bundle (`bundleRelease`, `.aab`), what Google Play takes,\n * rather than an APK. Signed with CRAFT_ANDROID_KEYSTORE,\n * CRAFT_ANDROID_KEYSTORE_PASSWORD, CRAFT_ANDROID_KEY_ALIAS and\n * CRAFT_ANDROID_KEY_PASSWORD when they are set.\n */\n bundle?: boolean\n compile?: boolean\n /** Same meaning as `InitOptions.runtimeDir`; refreshes what init installed. */\n runtimeDir?: string | null\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 renderer: 'web',\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 enableBluetooth: false,\n enableHealthConnect: false,\n enableKeepAwake: false,\n enableDeepLinks: false,\n urlSchemes: [],\n appLinks: [],\n trustedOrigins: [],\n minSdk: ANDROID_MIN_SDK,\n compileSdk: 36,\n targetSdk: 35,\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll('\\'', ''')\n}\n\nfunction escapeKotlinString(value: string): string {\n return value\n .replaceAll('\\\\', '\\\\\\\\')\n .replaceAll('\"', '\\\\\"')\n .replaceAll('$', '\\\\$')\n .replaceAll('\\r', '\\\\r')\n .replaceAll('\\n', '\\\\n')\n}\n\nfunction generatedPackageSegment(name: string): string {\n const normalized = name.toLowerCase().replace(/[^a-z0-9_]/g, '')\n if (!normalized) return 'app'\n return /^[a-z_]/.test(normalized) ? normalized : `app${normalized}`\n}\n\nfunction generatedGradleProjectName(name: string): string {\n const invalidCharacters = ['/', '\\\\', ':', '<', '>', '\"', '?', '*', '|']\n const normalized = invalidCharacters.reduce(\n (value, character) => value.replaceAll(character, '-'),\n name,\n ).trim()\n return normalized || 'craft-app'\n}\n\nfunction requireRegularFile(path: string, label: string): void {\n if (!existsSync(path)) throw new Error(`${label} not found: ${path}`)\n if (!statSync(path).isFile()) throw new Error(`${label} must be a file: ${path}`)\n}\n\nfunction containsPath(parent: string, candidate: string): boolean {\n const relativePath = relative(parent, candidate)\n return relativePath === ''\n || (relativePath !== '..' && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath))\n}\n\nfunction validateGoogleServicesFile(path: string, packageName: string): void {\n requireRegularFile(path, 'Google services file')\n\n let document: {\n client?: Array<{\n client_info?: { android_client_info?: { package_name?: string } }\n }>\n }\n try {\n document = JSON.parse(readFileSync(path, 'utf8'))\n }\n catch (error) {\n throw new Error(`Google services file must contain valid JSON: ${path}`, { cause: error })\n }\n\n const matchingClient = document.client?.some((client) => {\n return client.client_info?.android_client_info?.package_name === packageName\n })\n if (!matchingClient) {\n throw new Error(`Google services file has no client for Android package ${packageName}: ${path}`)\n }\n}\n\nfunction androidWebUrl(value: string, field: string): URL {\n let url: URL\n try {\n url = new URL(value)\n }\n catch {\n throw new Error(`Invalid ${field}: ${value}`)\n }\n\n const localDevelopment = url.protocol === 'http:' && LOCAL_DEVELOPMENT_HOSTS.has(url.hostname)\n if ((url.protocol !== 'https:' && !localDevelopment) || url.username || url.password) {\n throw new Error(`${field} must use HTTPS or local HTTP without credentials: ${value}`)\n }\n return url\n}\n\nfunction normalizeAndroidNetworkConfig(config: CraftAndroidConfig): void {\n const schemes = config.urlSchemes?.map(value => value.trim().toLowerCase()) ?? []\n if (schemes.some(value => !/^[a-z][a-z0-9+.-]*$/.test(value))) {\n throw new Error('Android deep-link schemes must be valid URI schemes')\n }\n config.urlSchemes = [...new Set(schemes)]\n config.appLinks = (config.appLinks ?? []).map((link) => {\n const host = link.host.trim().toLowerCase()\n const parsed = new URL(`https://${host}`)\n if (!host || parsed.hostname !== host || parsed.port || parsed.username || parsed.password || parsed.pathname !== '/' || parsed.search || parsed.hash)\n throw new Error('Android App Links need a hostname without a scheme, port or path')\n for (const rule of link.paths ?? []) {\n if (!rule.pattern.startsWith('/') || /[\\x00-\\x1f]/.test(rule.pattern) || (rule.exclude !== undefined && typeof rule.exclude !== 'boolean'))\n throw new Error('Android App Link path rules need an absolute path pattern and a boolean exclude')\n }\n return { ...link, host }\n })\n if (new Set(config.appLinks.map(link => link.host)).size !== config.appLinks.length)\n throw new Error('Android App Link hosts must be unique')\n if (config.enableDeepLinks && config.urlSchemes.length === 0 && config.appLinks.length === 0) {\n throw new Error('Android deep links require at least one URL scheme or an App Link host')\n }\n\n const trustedOrigins = (config.trustedOrigins ?? []).map((value) => {\n return androidWebUrl(value, 'Android trusted origin').origin\n })\n if (config.devServerURL) {\n const devServer = androidWebUrl(config.devServerURL, 'Android dev server URL')\n config.devServerURL = devServer.toString()\n trustedOrigins.push(devServer.origin)\n }\n config.trustedOrigins = [...new Set(trustedOrigins)]\n}\n\nfunction validateAndroidConfig(config: CraftAndroidConfig): void {\n if (!config.appName.trim()) throw new Error('Android app name must not be empty')\n if (config.renderer !== undefined && config.renderer !== 'web' && config.renderer !== 'native') {\n throw new Error(`Unknown Android renderer: ${config.renderer}. Expected web or native.`)\n }\n if (config.appearance !== undefined && !['light', 'dark', 'system'].includes(config.appearance)) {\n throw new Error(`Unknown Android appearance: ${config.appearance}. Expected light, dark, or system.`)\n }\n\n const packageSegments = config.packageName.split('.')\n if (packageSegments.length < 2\n || packageSegments.some(segment => !/^[A-Za-z_][A-Za-z0-9_]*$/.test(segment) || KOTLIN_KEYWORDS.has(segment))) {\n throw new Error(`Invalid Android package name: ${config.packageName}`)\n }\n\n if (!/^#(?:[\\dA-F]{3,4}|[\\dA-F]{6}|[\\dA-F]{8})$/i.test(config.backgroundColor ?? '')) {\n throw new Error(`Invalid Android background color: ${config.backgroundColor}`)\n }\n\n if (config.backgroundColorDark !== undefined && !config.backgroundColorDark.match(/^#[\\dA-F]{3,4}$|^#[\\dA-F]{6}$|^#[\\dA-F]{8}$/i)) {\n throw new Error(`Invalid Android dark background color: ${config.backgroundColorDark}`)\n }\n\n if (config.mapsApiKey !== undefined && !config.mapsApiKey.trim()) {\n throw new Error('Android mapsApiKey must not be empty')\n }\n\n for (const [name, value] of [\n ['versionCode', config.versionCode],\n ['minSdk', config.minSdk],\n ['compileSdk', config.compileSdk],\n ['targetSdk', config.targetSdk],\n ] as const) {\n if (!Number.isInteger(value) || Number(value) < 1) {\n throw new Error(`Android ${name} must be a positive integer`)\n }\n }\n\n if (Number(config.minSdk) < ANDROID_MIN_SDK) {\n throw new Error(`Android minSdk must be at least ${ANDROID_MIN_SDK} (Android 8.0) for the Craft runtime`)\n }\n if (Number(config.minSdk) > Number(config.targetSdk)) {\n throw new Error('Android minSdk must not exceed targetSdk')\n }\n if (Number(config.targetSdk) > Number(config.compileSdk)) {\n throw new Error('Android targetSdk must not exceed compileSdk')\n }\n}\n\nfunction normalizeNativeRoutePath(raw: unknown): string | null {\n if (typeof raw !== 'string') return null\n let path = raw\n const cut = path.search(/[?#]/)\n if (cut >= 0) path = path.slice(0, cut)\n if (!path.startsWith('/')) return null\n path = path.replace(/\\/{2,}/g, '/')\n if (path.length > 1 && path.endsWith('/')) path = path.slice(0, -1)\n return path\n}\n\nfunction nativeRouteSegments(path: string): string[] {\n return path === '/' ? [] : path.slice(1).split('/')\n}\n\nfunction matchesNativeRoute(nativeScreens: Record<string, string> | undefined, raw: string): boolean {\n const path = normalizeNativeRoutePath(raw)\n if (path === null) return false\n const parts = nativeRouteSegments(path)\n return Object.entries(nativeScreens ?? {}).some(([pattern, screen]) => {\n if (!screen) return false\n const normalized = normalizeNativeRoutePath(pattern)\n if (normalized === null) return false\n const route = nativeRouteSegments(normalized)\n for (let index = 0; index < route.length; index++) {\n const segment = route[index]\n if (segment === '*') return index === route.length - 1\n if (index >= parts.length) return false\n if (segment.startsWith(':')) {\n if (!parts[index]) return false\n }\n else if (segment !== parts[index]) return false\n }\n return route.length === parts.length\n })\n}\n\nfunction nativeStartPath(config: CraftAndroidConfig): string {\n if (!config.devServerURL) return '/'\n try {\n const url = new URL(config.devServerURL)\n return `${url.pathname || '/'}${url.search}`\n }\n catch {\n return '/'\n }\n}\n\nfunction validateNativeRouteConfig(config: CraftAndroidConfig): void {\n const screens = Object.entries(config.nativeScreens ?? {})\n for (const [pattern, screen] of screens) {\n if (normalizeNativeRoutePath(pattern) !== pattern || /[?#]/.test(pattern))\n throw new Error(`nativeScreens path \"${pattern}\" must be an absolute path with no query, hash or trailing slash.`)\n if (nativeRouteSegments(normalizeNativeRoutePath(pattern) ?? '/').slice(0, -1).includes('*'))\n throw new Error(`nativeScreens path \"${pattern}\" may only end in *.`)\n if (typeof screen !== 'string' || !/^[A-Za-z_$][\\w$-]*$/.test(screen))\n throw new Error(`nativeScreens screen \"${String(screen)}\" for \"${pattern}\" must be a screen name from the native bundle.`)\n }\n\n const ids = new Set<string>()\n for (const tab of config.tabs ?? []) {\n if (!tab || typeof tab.id !== 'string' || !tab.id || typeof tab.title !== 'string' || !tab.title)\n throw new Error('Each tab needs an id (its root path) and a title.')\n if (ids.has(tab.id)) throw new Error(`Tab \"${tab.id}\" is listed twice.`)\n ids.add(tab.id)\n }\n\n if (config.renderer === 'native' && screens.length > 0) {\n for (const id of ids) {\n if (!matchesNativeRoute(config.nativeScreens, id))\n throw new Error(`Tab \"${id}\" is not a nativeScreens path; with renderer: \"native\" every tab must be a native screen.`)\n }\n const start = nativeStartPath(config)\n if (!matchesNativeRoute(config.nativeScreens, start))\n throw new Error(`The start path \"${start}\" (from devServerURL) is not a nativeScreens path; with renderer: \"native\" the app must start on a native screen.`)\n }\n\n if (config.nativeApp !== undefined) {\n if (typeof config.nativeApp !== 'string' || !/^[A-Za-z_$][\\w$-]*$/.test(config.nativeApp))\n throw new Error(`nativeApp \"${String(config.nativeApp)}\" must be a route name from the native bundle.`)\n if (config.renderer !== 'native' && screens.length === 0)\n throw new Error('nativeApp runs in the native bundle; it needs nativeScreens or renderer: \"native\".')\n }\n}\n\nfunction writeAndroidConfig(output: string, config: CraftAndroidConfig): void {\n writeFileSync(join(output, 'craft.config.json'), JSON.stringify(config, null, 2))\n\n // Generator-only paths can reveal the developer machine's directory layout\n // and have no meaning to the installed app. Keep them in the project config\n // but never package them into the APK's assets.\n const runtimeConfig = { ...config }\n delete runtimeConfig.appIconPath\n delete runtimeConfig.googleServicesFile\n writeFileSync(\n join(output, 'app/src/main/assets/craft.config.json'),\n JSON.stringify(runtimeConfig, null, 2),\n )\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 sourceStat = statSync(sourcePath)\n if (!sourceStat.isDirectory() && !sourceStat.isFile()) {\n throw new Error(`Web asset path must be a file or directory: ${source}`)\n }\n\n const assetsDir = resolve(output, 'app/src/main/assets')\n if (containsPath(sourcePath, assetsDir) || containsPath(assetsDir, sourcePath)) {\n throw new Error(`Web asset source must not overlap generated asset directory: ${source}`)\n }\n if (sourceStat.isDirectory()) {\n requireRegularFile(join(sourcePath, 'index.html'), 'Web asset directory entry point')\n }\n\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 (sourceStat.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) healthConnectPermissions(config).forEach(permission => permissions.add(permission))\n // A background refresh (WorkManager) after a restart.\n if (config.renderer === 'native' && config.backgroundRefresh?.enabled) permissions.add('android.permission.RECEIVE_BOOT_COMPLETED')\n const bluetooth = config.enableBluetooth ? [\n ' <uses-permission android:name=\"android.permission.BLUETOOTH\" android:maxSdkVersion=\"30\" />',\n ' <uses-permission android:name=\"android.permission.BLUETOOTH_ADMIN\" android:maxSdkVersion=\"30\" />',\n ' <uses-permission android:name=\"android.permission.BLUETOOTH_SCAN\" android:usesPermissionFlags=\"neverForLocation\" />',\n ' <uses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\" />',\n ...(config.enableGeolocation ? [] : [' <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" android:maxSdkVersion=\"30\" />']),\n ].join('\\n') : ''\n return [bluetooth, ...[...permissions].map(permission => ` <uses-permission android:name=\"${permission}\" />`)].filter(Boolean).join('\\n')\n}\n\n/**\n * The native renderer's own sources: the app around the screens (routes,\n * stores, bars), its services (dialogs, browser, share, push, Health\n * Connect), background refresh, and Firebase messaging when push is on.\n */\nfunction writeNativeShellSources(output: string, packagePath: string, packageName: string, config: CraftAndroidConfig): void {\n const javaDir = join(output, 'app/src/main/java', packagePath)\n const write = (template: string, file: string, replace: (source: string) => string = source => source): void => {\n const source = readFileSync(join(TEMPLATES_DIR, template), 'utf-8').replace(/\\{\\{PACKAGE_NAME\\}\\}/g, packageName)\n writeFileSync(join(javaDir, file), replace(source))\n }\n write('CraftBluetooth.kt.template', 'CraftBluetooth.kt')\n write('CraftNativeShell.kt.template', 'CraftNativeShell.kt')\n write('CraftNativeImageCache.kt.template', 'CraftNativeImageCache.kt')\n write('CraftNativeBackground.kt.template', 'CraftNativeBackground.kt')\n write(config.enableHealthConnect ? 'CraftNativeHealth.kt.template' : 'CraftNativeHealthStub.kt.template', 'CraftNativeHealth.kt')\n write('CraftNativeServices.kt.template', 'CraftNativeServices.kt', source => source\n .replace('{{NATIVE_PUSH_IMPORTS}}', config.enablePushNotifications ? 'import com.google.firebase.messaging.FirebaseMessaging' : '')\n .replace('{{NATIVE_PUSH_REGISTER}}', config.enablePushNotifications\n ? ` try {\n FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->\n val token = if (task.isSuccessful) task.result else null\n if (token.isNullOrBlank()) done(failure(\"PUSH_UNAVAILABLE\", task.exception?.message ?: \"Firebase Cloud Messaging gave no token\"))\n else {\n context.getSharedPreferences(\"craft-native-push\", Context.MODE_PRIVATE).edit().putString(\"token\", token).apply()\n done(success(token))\n }\n }\n } catch (error: Exception) {\n done(failure(\"PUSH_UNAVAILABLE\", error.message ?: \"Firebase Cloud Messaging is not configured\"))\n }`\n : ' done(failure(\"CAPABILITY_DISABLED\", \"Push notifications are disabled\"))'))\n if (config.enablePushNotifications) write('CraftNativeMessagingService.kt.template', 'CraftNativeMessagingService.kt')\n else rmSync(join(javaDir, 'CraftNativeMessagingService.kt'), { force: true })\n // Files a screen shares (craft.files' copies, in the cache) go to other apps through this provider.\n mkdirSync(join(output, 'app/src/main/res/xml'), { recursive: true })\n writeFileSync(join(output, 'app/src/main/res/xml/craft_file_paths.xml'), `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n <cache-path name=\"craft-files\" path=\"craft-files/\" />\n <files-path name=\"craft-media\" path=\"craft-media/\" />\n</paths>\n`)\n}\n\n/** What the native renderer adds to the manifest's application element. */\nfunction renderNativeShellManifest(config: CraftAndroidConfig): string {\n const parts = [\n ' <receiver android:name=\".CraftNativeNotificationReceiver\" android:exported=\"false\" />',\n ` <provider\n android:name=\"androidx.core.content.FileProvider\"\n android:authorities=\"${config.packageName}.craftfiles\"\n android:exported=\"false\"\n android:grantUriPermissions=\"true\">\n <meta-data android:name=\"android.support.FILE_PROVIDER_PATHS\" android:resource=\"@xml/craft_file_paths\" />\n </provider>`,\n ]\n if (config.enablePushNotifications) {\n parts.push(` <service android:name=\".CraftNativeMessagingService\" android:exported=\"false\">\n <intent-filter>\n <action android:name=\"com.google.firebase.MESSAGING_EVENT\" />\n </intent-filter>\n </service>`)\n }\n if (config.enableHealthConnect) {\n // Android 14 and later open the app's privacy policy from Health\n // Connect's permission screen through this alias.\n parts.push(` <activity-alias\n android:name=\"ViewPermissionUsageActivity\"\n android:exported=\"true\"\n android:targetActivity=\".MainActivity\"\n android:permission=\"android.permission.START_VIEW_PERMISSION_USAGE\">\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 </activity-alias>`)\n }\n return parts.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 const custom = 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 const verified = (config.appLinks ?? []).map(link => {\n if (!/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/i.test(link.host)) throw new Error('Invalid Android App Link host')\n return ` <intent-filter android:autoVerify=\"true\">\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=\"https\" android:host=\"${link.host}\" />\n </intent-filter>`\n }).join('\\n')\n return [custom, verified].filter(Boolean).join('\\n')\n}\n\n/**\n * Initialize a new Android project\n */\n/**\n * The ABI directories a runtime directory is expected to hold.\n *\n * These are Android's own names for the two architectures craft builds, and\n * they are also `zig build build-android-all`'s output layout — `zig-out/android/`\n * already contains `arm64-v8a/libcraft.so` and `x86_64/libcraft.so`, which is\n * exactly the shape `app/src/main/jniLibs/` wants. So the install below is a\n * copy and not a rearrangement.\n *\n * x86_64 is not optional-in-practice: every Android emulator anyone tests on\n * is x86_64, so a runtime shipped for arm64 alone is one that never runs in\n * CI and never runs on a developer's machine either.\n */\nconst RUNTIME_ABIS = ['arm64-v8a', 'x86_64'] as const\n\n/**\n * Where the runtime directory comes from when the caller does not say.\n *\n * The same shape as `CRAFT_BIN` and the iOS builder's `CRAFT_IOS_RUNTIME`: an\n * explicit override for the monorepo dev loop, not a lookup path. Shipping the\n * runtime to real apps means putting these libraries in the pantry package\n * beside the `craft` binary, which is a distribution decision this function\n * does not make.\n */\nexport function resolveRuntimeDir(override?: string | null): string | null {\n if (override === null) return null\n const dir = override ?? process.env.CRAFT_ANDROID_RUNTIME\n if (!dir) return null\n if (!existsSync(dir)) {\n const source = override === undefined ? 'CRAFT_ANDROID_RUNTIME points at' : 'runtimeDir is'\n throw new Error(`${source} ${dir}, which does not exist.`)\n }\n return dir\n}\n\n/**\n * Copy the Zig runtime into the generated project as\n * `app/src/main/jniLibs/<abi>/libcraft.so`.\n *\n * That path is AGP's default `jniLibs.srcDirs`, so nothing in the Gradle\n * templates has to know about it — the library is packaged into the APK and\n * `System.loadLibrary(\"craft\")` finds it because the file is named for the\n * `craft` it asks for.\n *\n * Until this existed, nothing put the library anywhere. `CraftNative` caught\n * the `UnsatisfiedLinkError`, set `isAvailable = false`, and every action fell\n * through to the Kotlin shim — by design, so an app with no runtime still\n * works, which is also why nobody noticed that *every* generated app was in\n * that state and the whole Android half of the Zig bridge had never run.\n *\n * Returns true when a runtime was installed.\n */\nexport function installRuntime(output: string, runtimeDir: string): boolean {\n // Every ABI resolved before anything is written, so a half-populated\n // runtime directory cannot leave a project carrying one architecture and\n // claiming both. A single-ABI APK installs fine and then fails to load on\n // the other kind of device, which is the break that only shows up on\n // somebody else's phone.\n const found = RUNTIME_ABIS.map(abi => ({ abi, source: join(runtimeDir, abi, 'libcraft.so') }))\n .filter(entry => existsSync(entry.source))\n\n if (found.length === 0) {\n throw new Error(\n `${runtimeDir} has no <abi>/libcraft.so for any of ${RUNTIME_ABIS.join(', ')}. `\n + 'Run `zig build build-android-all -Doptimize=ReleaseSafe` in packages/zig and point at its zig-out/android.',\n )\n }\n\n if (found.length < RUNTIME_ABIS.length) {\n const missing = RUNTIME_ABIS.filter(abi => !found.some(entry => entry.abi === abi))\n console.warn(\n ` ⚠ only ${found.map(entry => entry.abi).join(', ')} was found; ${missing.join(', ')} is missing. `\n + 'The app will fall back to the Kotlin shim on those devices.',\n )\n }\n\n const dest = join(output, 'app/src/main/jniLibs')\n // Wiped rather than merged, so an ABI dropped from a later build does not\n // linger in the project as a stale library the APK still ships.\n rmSync(dest, { force: true, recursive: true })\n\n for (const { abi, source } of found) {\n const abiDir = join(dest, abi)\n mkdirSync(abiDir, { recursive: true })\n cpSync(source, join(abiDir, 'libcraft.so'))\n }\n\n return true\n}\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.${generatedPackageSegment(name)}`\n const packagePath = finalPackageName.replace(/\\./g, '/')\n\n // Command-level identity always wins over duplicate fields in `config`.\n // Normalize implied capabilities before validation so craft.config.json and\n // the generated Gradle source describe the same effective project.\n // Undefined keeps the default instead of erasing it (see the iOS builder).\n const given = Object.fromEntries(Object.entries(options.config ?? {}).filter(([, value]) => value !== undefined))\n const config: CraftAndroidConfig = {\n ...DEFAULT_CONFIG,\n ...given,\n appName: name,\n packageName: finalPackageName,\n }\n if (config.enableBackgroundLocation) config.enableGeolocation = true\n if (config.enableHealthConnect) {\n config.compileSdk = Math.max(config.compileSdk ?? 36, 36)\n config.enableHealthConnectBackground = Boolean(config.healthConnect?.background)\n config.enableHealthConnectHistory = Boolean(config.healthConnect?.history)\n healthConnectPermissions(config)\n }\n normalizeAndroidNetworkConfig(config)\n validateAndroidConfig(config)\n validateNativeRouteConfig(config)\n if (config.enablePushNotifications && !config.googleServicesFile) {\n throw new Error('Android push notifications require a googleServicesFile')\n }\n if (config.googleServicesFile) validateGoogleServicesFile(config.googleServicesFile, finalPackageName)\n if (config.appIconPath) requireRegularFile(config.appIconPath, 'App icon')\n const appIconExtension = config.appIconPath ? extname(config.appIconPath).toLowerCase() : undefined\n if (config.appIconPath && !['.gif', '.jpg', '.png', '.webp'].includes(appIconExtension ?? '')) {\n throw new Error(`Unsupported Android app icon format: ${appIconExtension || '(none)'}`)\n }\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/values-night'),\n join(output, 'app/src/main/res/drawable'),\n join(output, 'app/src/main/assets'),\n join(output, 'gradle/wrapper'),\n ]\n\n if (existsSync(output) && !statSync(output).isDirectory()) {\n throw new Error(`Android project output must be a directory: ${output}`)\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 writeAndroidConfig(output, config)\n\n const hasGoogleServices = Boolean(config.googleServicesFile)\n if (config.googleServicesFile) {\n cpSync(config.googleServicesFile, join(output, 'app/google-services.json'))\n }\n\n // Copy templates\n const mainActivityTemplate = readFileSync(join(\n TEMPLATES_DIR,\n config.renderer === 'native' ? 'MainActivityNative.kt.template' : 'MainActivity.kt.template',\n ), '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 writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftAppLinks.kt'),\n readFileSync(join(TEMPLATES_DIR, 'CraftAppLinks.kt.template'), 'utf8').replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n // Both renderers: crash reporting does not depend on how screens are drawn.\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftCrashReporter.kt'),\n readFileSync(join(TEMPLATES_DIR, 'CraftCrashReporter.kt.template'), 'utf-8')\n .replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName)\n .replace(/\\{\\{CRAFT_VERSION\\}\\}/g, CRAFT_ANDROID_VERSION),\n )\n if (config.renderer === 'native') {\n const mutationTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeMutation.kt.template'), 'utf-8')\n const flatListTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeFlatList.kt.template'), 'utf-8')\n const refreshableFlatListTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeRefreshableFlatList.kt.template'), 'utf-8')\n const capabilityTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeCapabilities.kt.template'), 'utf-8')\n const notificationTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeNotificationReceiver.kt.template'), 'utf-8')\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeMutation.kt'),\n mutationTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeFlatList.kt'),\n flatListTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeRefreshableFlatList.kt'),\n refreshableFlatListTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeCapabilities.kt'),\n capabilityTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeNotificationReceiver.kt'),\n notificationTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeNativeShellSources(output, packagePath, finalPackageName, config)\n }\n\n // Create CraftBridge.kt\n const craftBridgeTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftBridge.kt.template'), 'utf-8')\n const craftBridge = craftBridgeTemplate\n .replace(/\\{\\{PROMISE_RUNTIME\\}\\}/g, () => renderAndroidPromiseRuntime(' '))\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 evaluatePromiseJavascript(\"$callback && $callback($payload)\")\n }\n } catch (error: Exception) {\n val payload = JSONObject.quote(error.message ?: \"Firebase Cloud Messaging is not configured\")\n evaluatePromiseJavascript(\"window._craftPushReject && window._craftPushReject($payload)\")\n }\n }`\n : `evaluatePromiseJavascript(\n \"window._craftPushReject && window._craftPushReject('Push notifications are disabled')\"\n )`)\n writeFileSync(join(output, 'app/src/main/java', packagePath, 'CraftBridge.kt'), craftBridge)\n\n // CraftNative goes to a fixed package, not the app's. The prebuilt\n // libcraft.so binds its natives by class name in JNI_OnLoad, and it cannot\n // know a name chosen here — so this one file is deliberately not templated\n // and deliberately not under packagePath.\n const craftNative = readFileSync(join(TEMPLATES_DIR, 'CraftNative.kt.template'), 'utf-8')\n const nativeDir = join(output, 'app/src/main/java/com/craft/runtime')\n mkdirSync(nativeDir, { recursive: true })\n writeFileSync(join(nativeDir, 'CraftNative.kt'), craftNative)\n // The bridge and native holder both reference the recording store/service,\n // even when the service is not registered in the manifest. Keep these\n // support types in the same fixed package as CraftNative so every generated\n // app compiles regardless of its chosen package name or feature flags.\n const serviceTemplate = readFileSync(join(TEMPLATES_DIR, 'LocationRecordingService.kt.template'), 'utf-8')\n writeFileSync(join(nativeDir, 'LocationRecordingService.kt'), serviceTemplate)\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 // 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 // Plain HTTP only for a local dev server, or an app that talks to the\n // person's own devices on the LAN (which have no public certificate).\n .replace(/\\{\\{USES_CLEARTEXT\\}\\}/g, config.devServerURL?.startsWith('http://') || config.enableLocalNetwork ? 'true' : 'false')\n .replace(/\\{\\{DEEP_LINK_INTENT_FILTERS\\}\\}/g, renderAndroidDeepLinks(config))\n .replace(/\\{\\{BACKGROUND_SERVICE\\}\\}/g, config.enableBackgroundLocation\n ? ' <service android:name=\"com.craft.runtime.LocationRecordingService\" android:exported=\"false\" android:foregroundServiceType=\"location\" android:stopWithTask=\"false\" />'\n : '')\n .replace(/\\{\\{NATIVE_NOTIFICATION_RECEIVER\\}\\}/g, config.renderer === 'native'\n ? renderNativeShellManifest(config)\n : '')\n // Starts the crash reporter before Application.onCreate, for every way\n // the process starts (CraftCrashReporter.kt). Inert without a key.\n .replace(/\\{\\{CRASH_REPORTER_PROVIDER\\}\\}/g, ` <provider android:name=\".CraftCrashReporterInit\" android:authorities=\"\\${applicationId}.craft-crash\" android:exported=\"false\" android:initOrder=\"1000\" />`)\n .replace(/\\{\\{GOOGLE_MAPS_API_KEY\\}\\}/g, config.mapsApiKey\n ? ` <meta-data android:name=\"com.google.android.geo.API_KEY\" android:value=\"${escapeXml(config.mapsApiKey)}\" />`\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>${config.renderer === 'native' ? '' : `\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, escapeKotlinString(config.version || '1.0.0'))\n .replace(/\\{\\{VERSION_CODE\\}\\}/g, String(config.versionCode || 1))\n .replace(/\\{\\{MIN_SDK\\}\\}/g, String(config.minSdk ?? ANDROID_MIN_SDK))\n .replace(/\\{\\{COMPILE_SDK\\}\\}/g, String(config.compileSdk || 36))\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(/\\{\\{REFRESH_LAYOUT_DEPENDENCY\\}\\}/g, config.renderer === 'native'\n ? ' implementation(\"androidx.swiperefreshlayout:swiperefreshlayout:1.1.0\")'\n : '')\n .replace(/\\{\\{GOOGLE_MAPS_DEPENDENCY\\}\\}/g, config.renderer === 'native'\n ? ' // Native STX Map overlays and trail routes\\n implementation(\"com.google.android.gms:play-services-maps:18.2.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 .replace(/\\{\\{JAVASCRIPT_ENGINE_DEPENDENCY\\}\\}/g, config.renderer === 'native'\n ? ` implementation(\"androidx.javascriptengine:javascriptengine:1.1.0\")\n // Native background refresh (WorkManager).\n implementation(\"androidx.work:work-runtime-ktx:2.9.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(\n /\\{\\{APP_NAME\\}\\}/g,\n escapeKotlinString(generatedGradleProjectName(name)),\n )\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\">${escapeXml(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 const darkColorsPath = join(output, 'app/src/main/res/values-night/colors.xml')\n if (config.backgroundColorDark) {\n writeFileSync(darkColorsPath, `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"background\">${config.backgroundColorDark}</color>\n</resources>\n`)\n }\n else {\n rmSync(darkColorsPath, { force: true })\n }\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 cpSync(\n config.appIconPath,\n join(output, `app/src/main/res/drawable/craft_app_icon${appIconExtension}`),\n )\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 = config.renderer === 'native'\n ? `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:id=\"@+id/native_root\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"match_parent\" />\n`\n : `<?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>${escapeXml(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>⚡ ${escapeXml(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 // The Zig runtime, when one was configured. Absent, the generated app is the\n // Kotlin shim and behaves exactly as it always has — which is the fallback\n // `CraftNative` was written for, not a failure.\n const runtimeDir = resolveRuntimeDir(options.runtimeDir)\n if (runtimeDir) {\n installRuntime(output, runtimeDir)\n console.log(' Installed the Zig runtime from', runtimeDir)\n }\n\n console.log('✅ Project initialized')\n console.log('')\n console.log('Next steps:')\n console.log(` 1. cd ${output}`)\n if (config.renderer === 'native') {\n console.log(' 2. Compile your screen: stx native compile Screen.stx --format bundle --output screen.js')\n console.log(' 3. Run: craft android build --native-bundle screen.js')\n }\n else {\n console.log(' 2. Add your web content to app/src/main/assets/index.html')\n console.log(' 3. Run: craft android build')\n }\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, nativeBundlePath, devServer, output, release, bundle, 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 validateAndroidConfig(config)\n validateNativeRouteConfig(config)\n\n if (config.renderer === 'native') {\n if (htmlPath || devServer) throw new Error('A native Android screen cannot use --html-path or --dev-server.')\n const destination = join(output, 'app/src/main/assets/native-screen.js')\n if (nativeBundlePath && resolve(nativeBundlePath) !== resolve(destination)) cpSync(nativeBundlePath, destination)\n const icons = join(output, 'app/src/main/assets/craft-icons.json')\n if (options.iconsPath) {\n requireRegularFile(options.iconsPath, 'Icon table')\n if (resolve(options.iconsPath) !== resolve(icons)) cpSync(options.iconsPath, icons)\n }\n if (!existsSync(destination)) {\n throw new Error('Native Android mode needs app/src/main/assets/native-screen.js. Compile a .stx screen with `stx native compile` first.')\n }\n }\n else if (nativeBundlePath) {\n throw new Error('--native-bundle requires renderer: \"native\" in craft.config.json.')\n }\n\n // Refresh the runtime the same way the iOS builder does: only for a project\n // that already has one, so `craft android build` in a shim-only project does\n // not quietly acquire a native library it was never generated with.\n if (existsSync(join(output, 'app/src/main/jniLibs'))) {\n const runtimeDir = resolveRuntimeDir(options.runtimeDir)\n if (runtimeDir) {\n installRuntime(output, runtimeDir)\n console.log(' Refreshed the Zig runtime from', runtimeDir)\n }\n else {\n console.log(' Keeping the Zig runtime installed at init (no runtime directory configured)')\n }\n }\n\n // Update dev server URL if provided\n if (devServer) {\n const url = androidWebUrl(devServer, 'Android dev server URL')\n config.devServerURL = url.toString()\n config.trustedOrigins = [...new Set([\n ...(config.trustedOrigins ?? []).map(value => androidWebUrl(value, 'Android trusted origin').origin),\n url.origin,\n ])]\n writeAndroidConfig(output, config)\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 writeAndroidConfig(output, config)\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 variant = release ? 'Release' : 'Debug'\n const buildType = `${bundle ? 'bundle' : 'assemble'}${variant}`\n console.log(` Building ${release ? 'release' : 'debug'} ${bundle ? 'app bundle' : 'APK'}...`)\n if (release && !process.env.CRAFT_ANDROID_KEYSTORE)\n console.log(' CRAFT_ANDROID_KEYSTORE is not set: the release build is unsigned.')\n\n try {\n await $`cd ${output} && ./gradlew ${buildType}`\n const artifact = bundle\n ? `app/build/outputs/bundle/${variant.toLowerCase()}/app-${variant.toLowerCase()}.aab`\n : release\n ? 'app/build/outputs/apk/release/app-release.apk'\n : 'app/build/outputs/apk/debug/app-debug.apk'\n console.log(`✅ ${bundle ? 'App bundle' : 'APK'} built: ${artifact}`)\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
6
|
"const ANDROID_PROMISE_RUNTIME = `\nif (window.__craftRejectPendingPromises) {\n window.__craftRejectPendingPromises('Android bridge reinitialized');\n}\nif (window.__craftRejectPermissionRequests) {\n window.__craftRejectPermissionRequests('Android bridge reinitialized');\n}\nwindow.__craftPromiseRuntimeClosed = false;\nwindow.__craftPendingPromises = Object.create(null);\nwindow.__craftPromise = function(channel, resolveName, rejectName, invoke, timeoutMs, timeoutError) {\n if (window.__craftPromiseRuntimeClosed) {\n return Promise.reject(new Error('Android bridge is closed'));\n }\n if (window.__craftPendingPromises[channel]) {\n return Promise.reject(new Error('A '.concat(channel, ' request is already in progress')));\n }\n\n return new Promise(function(resolve, reject) {\n var entry = {settled: false, timer: null, settle: null};\n var resolveCallback;\n var rejectCallback;\n\n entry.settle = function(succeeded, value) {\n if (entry.settled || window.__craftPendingPromises[channel] !== entry) return;\n entry.settled = true;\n if (entry.timer !== null) clearTimeout(entry.timer);\n if (window[resolveName] === resolveCallback) window[resolveName] = null;\n if (window[rejectName] === rejectCallback) window[rejectName] = null;\n delete window.__craftPendingPromises[channel];\n if (succeeded) resolve(value);\n else reject(value);\n };\n\n resolveCallback = function(value) {\n entry.settle(true, value);\n };\n rejectCallback = function(error) {\n entry.settle(false, error);\n };\n window[resolveName] = resolveCallback;\n window[rejectName] = rejectCallback;\n window.__craftPendingPromises[channel] = entry;\n\n if (timeoutMs > 0) {\n entry.timer = setTimeout(function() {\n entry.settle(false, timeoutError || new Error(channel.concat(' request timed out')));\n }, timeoutMs);\n }\n\n try {\n invoke();\n }\n catch (error) {\n entry.settle(false, error);\n }\n });\n};\n\nwindow.__craftRejectPendingPromises = function(message) {\n window.__craftPromiseRuntimeClosed = true;\n Object.keys(window.__craftPendingPromises).forEach(function(channel) {\n var entry = window.__craftPendingPromises[channel];\n if (entry) entry.settle(false, new Error(message || 'Android bridge closed'));\n });\n};\n`\n\nexport function renderAndroidPromiseRuntime(indent = ''): string {\n return ANDROID_PROMISE_RUNTIME\n .trim()\n .split('\\n')\n .map(line => `${indent}${line}`)\n .join('\\n')\n}\n\nexport { ANDROID_PROMISE_RUNTIME }\n",
|
|
7
7
|
"/** Compile and execute a native Kotlin fixture with Gradle's dependency resolver. */\nimport { readFileSync } from 'node:fs'\nimport { mkdtemp, rm, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { dirname, join } from 'node:path'\n\n/** Uses the Android builder's Kotlin version, including its transitive compiler dependencies. */\nexport async function runKotlinFixture(source: string): Promise<void> {\n const gradle = Bun.which('gradle')\n if (!gradle) throw new Error('Gradle is required to run native Kotlin fixtures')\n const template = readFileSync(join(dirname(import.meta.dir), 'templates/build.gradle.kts.project.template'), 'utf8')\n const version = template.match(/id\\(\"org.jetbrains.kotlin.android\"\\) version \"([\\d.]+)\"/)?.[1]\n if (!version) throw new Error('The Android builder Kotlin version is missing')\n const directory = await mkdtemp(join(tmpdir(), 'craft-kotlin-fixture-'))\n try {\n await writeFile(join(directory, 'Fixture.kt'), source)\n await writeFile(join(directory, 'settings.gradle'), \"rootProject.name = 'craft-kotlin-fixture'\\n\")\n await writeFile(join(directory, 'gradle.properties'), 'org.gradle.jvmargs=-Xmx512m\\n')\n await writeFile(join(directory, 'build.gradle'), `\nrepositories { mavenCentral() }\nconfigurations {\n kotlinCompiler\n kotlinRuntime\n}\ndependencies {\n kotlinCompiler 'org.jetbrains.kotlin:kotlin-compiler-embeddable:${version}'\n kotlinRuntime 'org.jetbrains.kotlin:kotlin-stdlib:${version}'\n}\ntasks.register('compileFixture', JavaExec) {\n classpath = configurations.kotlinCompiler\n mainClass = 'org.jetbrains.kotlin.cli.jvm.K2JVMCompiler'\n args '-no-stdlib', '-no-reflect', '-classpath', configurations.kotlinRuntime.asPath,\n '-d', layout.buildDirectory.dir('classes').get().asFile.absolutePath,\n file('Fixture.kt').absolutePath\n}\ntasks.register('runFixture', JavaExec) {\n dependsOn 'compileFixture'\n classpath = files(layout.buildDirectory.dir('classes')) + configurations.kotlinRuntime\n mainClass = 'FixtureKt'\n}\n`)\n const child = Bun.spawn([gradle, '--no-daemon', '--console=plain', '--max-workers=2', 'runFixture'], {\n cwd: directory,\n stdout: 'inherit',\n stderr: 'inherit',\n })\n const code = await child.exited\n if (code !== 0) throw new Error(`Native Kotlin fixture failed with exit code ${code}`)\n }\n finally {\n await rm(directory, { recursive: true, force: true })\n }\n}\n"
|
|
8
8
|
],
|
|
9
|
-
"mappings": "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",
|
|
10
|
-
"debugId": "
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "E085D9D53817707064756E2164756E21",
|
|
11
11
|
"names": []
|
|
12
12
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
package {{PACKAGE_NAME}}
|
|
2
|
+
|
|
3
|
+
import java.io.File
|
|
4
|
+
import java.io.BufferedInputStream
|
|
5
|
+
import java.io.DataInputStream
|
|
6
|
+
import java.io.DataOutputStream
|
|
7
|
+
import java.io.IOException
|
|
8
|
+
import java.io.OutputStream
|
|
9
|
+
import java.security.MessageDigest
|
|
10
|
+
import java.util.Locale
|
|
11
|
+
|
|
12
|
+
internal data class CraftNativeImagePolicy(val expiresAt: Long)
|
|
13
|
+
|
|
14
|
+
internal fun craftNativeImagePolicy(
|
|
15
|
+
requestHeaders: Map<String, String>,
|
|
16
|
+
responseHeaders: Map<String, List<String>>,
|
|
17
|
+
now: Long,
|
|
18
|
+
): CraftNativeImagePolicy? {
|
|
19
|
+
// Persist only the anonymous content the server explicitly calls public.
|
|
20
|
+
// A custom header can carry an account credential just like Authorization.
|
|
21
|
+
if (requestHeaders.isNotEmpty()) return null
|
|
22
|
+
fun values(name: String) = responseHeaders.entries
|
|
23
|
+
.filter { it.key.equals(name, ignoreCase = true) }.flatMap { it.value }
|
|
24
|
+
val variants = values("Vary").flatMap { it.split(',') }.map { it.trim().lowercase(Locale.ROOT) }
|
|
25
|
+
if (values("Set-Cookie").isNotEmpty() || variants.any { it in setOf("*", "cookie", "authorization") }) return null
|
|
26
|
+
val directives = values("Cache-Control").flatMap { it.split(',') }.map { it.trim().lowercase(Locale.ROOT) }
|
|
27
|
+
if ("public" !in directives || directives.any { it.substringBefore('=').trim() in setOf("private", "no-store", "no-cache") }) return null
|
|
28
|
+
val ages = directives.filter { it.substringBefore('=').trim() == "max-age" }
|
|
29
|
+
if (ages.isEmpty()) return null
|
|
30
|
+
val seconds = ages.map {
|
|
31
|
+
it.substringAfter('=', "").trim().removeSurrounding("\"").toLongOrNull()?.takeIf { age -> age > 0 } ?: return null
|
|
32
|
+
}.min().coerceAtMost(31_536_000L)
|
|
33
|
+
val ageHeader = values("Age").firstOrNull()
|
|
34
|
+
val age = if (ageHeader == null) 0L else ageHeader.trim().toLongOrNull()?.takeIf { it >= 0 } ?: return null
|
|
35
|
+
val remaining = seconds - age
|
|
36
|
+
if (remaining <= 0) return null
|
|
37
|
+
return runCatching { CraftNativeImagePolicy(Math.addExact(now, remaining * 1000)) }.getOrNull()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
internal class CraftNativeImageCache(
|
|
41
|
+
private val root: File,
|
|
42
|
+
private val maxBytes: Long = 32L * 1024 * 1024,
|
|
43
|
+
private val maxEntryBytes: Long = 8L * 1024 * 1024,
|
|
44
|
+
private val maxEntries: Int = 128,
|
|
45
|
+
private val now: () -> Long = System::currentTimeMillis,
|
|
46
|
+
) {
|
|
47
|
+
init {
|
|
48
|
+
require(maxBytes > 0 && maxEntryBytes > 0 && maxEntries > 0)
|
|
49
|
+
// A process killed while encoding can leave a staging file behind.
|
|
50
|
+
// All instances share this lock, so another writer cannot own it now.
|
|
51
|
+
synchronized(lock) {
|
|
52
|
+
runCatching {
|
|
53
|
+
root.listFiles().orEmpty().filter { it.name.endsWith(".tmp") }.forEach(File::delete)
|
|
54
|
+
prune()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private fun file(address: String): File {
|
|
60
|
+
val digest = MessageDigest.getInstance("SHA-256").digest(address.toByteArray(Charsets.UTF_8))
|
|
61
|
+
val name = digest.joinToString("") { (it.toInt() and 255).toString(16).padStart(2, '0') }
|
|
62
|
+
return File(root, "$name.cache")
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fun read(address: String): ByteArray? = synchronized(lock) {
|
|
66
|
+
runCatching {
|
|
67
|
+
val target = file(address)
|
|
68
|
+
if (!target.isFile) return@runCatching null
|
|
69
|
+
if (target.length() <= HEADER_BYTES || target.length() > HEADER_BYTES + minOf(maxEntryBytes, maxBytes)) {
|
|
70
|
+
target.delete()
|
|
71
|
+
return@runCatching null
|
|
72
|
+
}
|
|
73
|
+
val data = DataInputStream(BufferedInputStream(target.inputStream())).use { input ->
|
|
74
|
+
if (input.readInt() != MAGIC || input.readLong() <= now()) null else input.readBytes()
|
|
75
|
+
}
|
|
76
|
+
if (data == null || data.isEmpty()) { target.delete(); null }
|
|
77
|
+
else { target.setLastModified(now()); data }
|
|
78
|
+
}.getOrNull()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fun write(address: String, policy: CraftNativeImagePolicy, encode: (OutputStream) -> Boolean): Boolean = synchronized(lock) {
|
|
82
|
+
var staging: File? = null
|
|
83
|
+
try {
|
|
84
|
+
if (policy.expiresAt <= now() || (!root.isDirectory && !root.mkdirs())) return@synchronized false
|
|
85
|
+
val target = file(address)
|
|
86
|
+
staging = File.createTempFile(target.name, ".tmp", root)
|
|
87
|
+
staging.outputStream().buffered().use { raw ->
|
|
88
|
+
val header = DataOutputStream(raw)
|
|
89
|
+
header.writeInt(MAGIC)
|
|
90
|
+
header.writeLong(policy.expiresAt)
|
|
91
|
+
val payload = BoundedImageOutput(raw, minOf(maxEntryBytes, maxBytes))
|
|
92
|
+
if (!encode(payload) || payload.written == 0L) throw IOException("Incomplete cached image")
|
|
93
|
+
payload.flush()
|
|
94
|
+
}
|
|
95
|
+
// Freshness and pixels are committed together; a failed write
|
|
96
|
+
// never replaces an earlier, complete image with a partial file.
|
|
97
|
+
if (!staging.renameTo(target)) return@synchronized false
|
|
98
|
+
target.setLastModified(now())
|
|
99
|
+
prune()
|
|
100
|
+
true
|
|
101
|
+
}
|
|
102
|
+
catch (error: Exception) { false }
|
|
103
|
+
catch (error: OutOfMemoryError) { false }
|
|
104
|
+
finally { staging?.delete() }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
fun remove(address: String) = synchronized(lock) { runCatching { file(address).delete() }; Unit }
|
|
108
|
+
|
|
109
|
+
private fun prune() {
|
|
110
|
+
val entries = root.listFiles().orEmpty().filter { entryName.matches(it.name) }.sortedBy(File::lastModified)
|
|
111
|
+
var count = entries.size
|
|
112
|
+
var bytes = entries.sumOf { (it.length() - HEADER_BYTES).coerceAtLeast(0) }
|
|
113
|
+
for (entry in entries) {
|
|
114
|
+
if (count <= maxEntries && bytes <= maxBytes) break
|
|
115
|
+
val size = (entry.length() - HEADER_BYTES).coerceAtLeast(0)
|
|
116
|
+
if (entry.delete()) { count--; bytes -= size }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private class BoundedImageOutput(private val output: OutputStream, private val limit: Long) : OutputStream() {
|
|
121
|
+
var written = 0L
|
|
122
|
+
private set
|
|
123
|
+
override fun write(value: Int) {
|
|
124
|
+
if (written >= limit) throw IOException("Cached image is too large")
|
|
125
|
+
output.write(value)
|
|
126
|
+
written++
|
|
127
|
+
}
|
|
128
|
+
override fun write(bytes: ByteArray, offset: Int, length: Int) {
|
|
129
|
+
if (written + length > limit) throw IOException("Cached image is too large")
|
|
130
|
+
output.write(bytes, offset, length)
|
|
131
|
+
written += length
|
|
132
|
+
}
|
|
133
|
+
override fun flush() = output.flush()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private companion object {
|
|
137
|
+
const val MAGIC = 0x43494d31
|
|
138
|
+
const val HEADER_BYTES = 12L
|
|
139
|
+
val entryName = Regex("^[a-f0-9]{64}\\.cache$")
|
|
140
|
+
val lock = Any()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -120,6 +120,7 @@ internal class CraftNativeServices(
|
|
|
120
120
|
done(success(CraftNativeMedia.prune(context, (0 until keep.length()).map { keep.optString(it) })))
|
|
121
121
|
}
|
|
122
122
|
"usage" -> done(success(CraftNativeMedia.usage(context)))
|
|
123
|
+
"clear" -> done(success(CraftNativeMedia.clear(context)))
|
|
123
124
|
else -> done(failure("UNKNOWN_ACTION", "Unsupported Media method"))
|
|
124
125
|
}
|
|
125
126
|
"Background" -> {
|
|
@@ -1021,7 +1021,8 @@ internal object CraftNativeSymbols {
|
|
|
1021
1021
|
* A native Video given an address with a copy here plays the copy.
|
|
1022
1022
|
*/
|
|
1023
1023
|
internal object CraftNativeMedia {
|
|
1024
|
-
private val
|
|
1024
|
+
private data class Flight(val id: String, val waiting: MutableList<(CraftNativeCapabilityResult) -> Unit>)
|
|
1025
|
+
private val inFlight = HashMap<String, Flight>()
|
|
1025
1026
|
private val executor = Executors.newFixedThreadPool(2)
|
|
1026
1027
|
private val main = Handler(Looper.getMainLooper())
|
|
1027
1028
|
|
|
@@ -1054,12 +1055,12 @@ internal object CraftNativeMedia {
|
|
|
1054
1055
|
if (!cellular && metered(context)) {
|
|
1055
1056
|
return done(CraftNativeCapabilityResult(data = JSONObject().put("ok", false).put("url", address).put("reason", "cellular")))
|
|
1056
1057
|
}
|
|
1057
|
-
synchronized(inFlight) {
|
|
1058
|
+
val flight = synchronized(inFlight) {
|
|
1058
1059
|
inFlight[address]?.let { waiting ->
|
|
1059
|
-
waiting.add(done)
|
|
1060
|
+
waiting.waiting.add(done)
|
|
1060
1061
|
return
|
|
1061
1062
|
}
|
|
1062
|
-
inFlight[address] =
|
|
1063
|
+
Flight(java.util.UUID.randomUUID().toString(), mutableListOf(done)).also { inFlight[address] = it }
|
|
1063
1064
|
}
|
|
1064
1065
|
executor.execute {
|
|
1065
1066
|
val result = runCatching {
|
|
@@ -1073,7 +1074,7 @@ internal object CraftNativeMedia {
|
|
|
1073
1074
|
val target = file(context, address)
|
|
1074
1075
|
target.parentFile?.mkdirs()
|
|
1075
1076
|
// Written aside and moved into place, so a half-written file is never played.
|
|
1076
|
-
val staging = File(target.parentFile, "${target.name}.part")
|
|
1077
|
+
val staging = File(target.parentFile, "${target.name}.${flight.id}.part")
|
|
1077
1078
|
try {
|
|
1078
1079
|
val size = connection.inputStream.use { input -> staging.outputStream().use { input.copyTo(it) } }
|
|
1079
1080
|
val expected = connection.contentLengthLong
|
|
@@ -1081,6 +1082,9 @@ internal object CraftNativeMedia {
|
|
|
1081
1082
|
return@runCatching CraftNativeCapabilityResult(code = "NETWORK_ERROR", message = "media.keep received an incomplete file")
|
|
1082
1083
|
}
|
|
1083
1084
|
synchronized(inFlight) {
|
|
1085
|
+
if (inFlight[address]?.id != flight.id) {
|
|
1086
|
+
return@runCatching CraftNativeCapabilityResult(code = "CANCELLED", message = "The video cache was cleared")
|
|
1087
|
+
}
|
|
1084
1088
|
if (!staging.renameTo(target)) {
|
|
1085
1089
|
return@runCatching CraftNativeCapabilityResult(code = "IO_ERROR", message = "media.keep could not store the file")
|
|
1086
1090
|
}
|
|
@@ -1098,7 +1102,7 @@ internal object CraftNativeMedia {
|
|
|
1098
1102
|
}
|
|
1099
1103
|
CraftNativeCapabilityResult(code = if (full) "NO_SPACE" else if (error is java.net.SocketTimeoutException) "TIMEOUT" else "NETWORK_ERROR", message = error.message ?: "media.keep failed")
|
|
1100
1104
|
}
|
|
1101
|
-
val waiting = synchronized(inFlight) { inFlight.remove(address)
|
|
1105
|
+
val waiting = synchronized(inFlight) { if (inFlight[address]?.id == flight.id) inFlight.remove(address)?.waiting.orEmpty() else emptyList() }
|
|
1102
1106
|
main.post { waiting.forEach { it(result) } }
|
|
1103
1107
|
}
|
|
1104
1108
|
}
|
|
@@ -1115,12 +1119,31 @@ internal object CraftNativeMedia {
|
|
|
1115
1119
|
var removed = 0
|
|
1116
1120
|
synchronized(inFlight) {
|
|
1117
1121
|
// A concurrent keep owns its target and staging file until it finishes.
|
|
1118
|
-
val active = inFlight.
|
|
1122
|
+
val active = inFlight.flatMap { (address, flight) ->
|
|
1123
|
+
val name = file(context, address).name
|
|
1124
|
+
listOf(name, "$name.${flight.id}.part")
|
|
1125
|
+
}.toSet()
|
|
1126
|
+
root(context).listFiles()?.forEach { file ->
|
|
1127
|
+
if (file.name !in active && file.name !in wanted && file.delete()) removed++
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
return removed
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/** Invalidates old transfers before deleting files; a later keep starts afresh. */
|
|
1134
|
+
fun clear(context: Context): Int {
|
|
1135
|
+
var removed = 0
|
|
1136
|
+
var failure: java.io.IOException? = null
|
|
1137
|
+
val waiting = synchronized(inFlight) {
|
|
1138
|
+
val callbacks = inFlight.values.flatMap { it.waiting }
|
|
1139
|
+
inFlight.clear()
|
|
1119
1140
|
root(context).listFiles()?.forEach { file ->
|
|
1120
|
-
|
|
1121
|
-
if (target !in active && file.name !in wanted && file.delete()) removed++
|
|
1141
|
+
if (file.delete()) removed++ else failure = java.io.IOException("The video cache could not be cleared")
|
|
1122
1142
|
}
|
|
1143
|
+
callbacks
|
|
1123
1144
|
}
|
|
1145
|
+
main.post { waiting.forEach { it(CraftNativeCapabilityResult(code = "CANCELLED", message = "The video cache was cleared")) } }
|
|
1146
|
+
failure?.let { throw it }
|
|
1124
1147
|
return removed
|
|
1125
1148
|
}
|
|
1126
1149
|
|
|
@@ -122,6 +122,8 @@ import com.google.android.gms.maps.model.Polyline
|
|
|
122
122
|
import com.google.android.gms.maps.model.PolylineOptions
|
|
123
123
|
import org.json.JSONArray
|
|
124
124
|
import org.json.JSONObject
|
|
125
|
+
import java.io.File
|
|
126
|
+
import java.net.HttpURLConnection
|
|
125
127
|
import java.net.URL
|
|
126
128
|
import java.text.SimpleDateFormat
|
|
127
129
|
import java.util.Calendar
|
|
@@ -3805,6 +3807,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
3805
3807
|
private var windowSafeInsets = intArrayOf(0, 0, 0, 0)
|
|
3806
3808
|
private var useMessagePorts = false
|
|
3807
3809
|
private val imageExecutor = Executors.newFixedThreadPool(2)
|
|
3810
|
+
private val imageCache by lazy { CraftNativeImageCache(File(cacheDir, "craft-native-images")) }
|
|
3808
3811
|
/** Lays out each isolate's prelude (the synchronous stores) off the main thread. */
|
|
3809
3812
|
private val preludeExecutor = Executors.newSingleThreadExecutor()
|
|
3810
3813
|
private val imageSources = mutableMapOf<ImageView, String>()
|
|
@@ -4088,6 +4091,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
4088
4091
|
show(target)
|
|
4089
4092
|
} else {
|
|
4090
4093
|
host.removeView(target.view)
|
|
4094
|
+
target.view.translationX = 0f
|
|
4091
4095
|
current.view.translationX = 0f
|
|
4092
4096
|
chrome?.selectTab(currentTab)
|
|
4093
4097
|
tabSwipeTarget = null
|
|
@@ -4156,13 +4160,22 @@ class MainActivity : AppCompatActivity() {
|
|
|
4156
4160
|
* page on the app's site in the in-app browser. Another site's address
|
|
4157
4161
|
* opens in the browser too.
|
|
4158
4162
|
*/
|
|
4159
|
-
private fun navigateOpen(target: String, fromLink: Boolean = false) {
|
|
4163
|
+
private fun navigateOpen(target: String, fromLink: Boolean = false, replace: Boolean = false) {
|
|
4160
4164
|
val path = routes.appPath(target, urlSchemes)
|
|
4161
4165
|
if (path == null) {
|
|
4162
4166
|
openExternal(target)
|
|
4163
4167
|
return
|
|
4164
4168
|
}
|
|
4165
4169
|
val match = routes.match(path)
|
|
4170
|
+
if (replace && match != null && screens.isNotEmpty()) {
|
|
4171
|
+
val stack = screens
|
|
4172
|
+
val removed = stack.removeAt(stack.lastIndex)
|
|
4173
|
+
val previous = stack.lastOrNull()
|
|
4174
|
+
closeScreen(removed) {
|
|
4175
|
+
if (screens === stack && stack.lastOrNull() === previous) navigateOpen(path, fromLink)
|
|
4176
|
+
}
|
|
4177
|
+
return
|
|
4178
|
+
}
|
|
4166
4179
|
val tab = routes.tabFor(path)
|
|
4167
4180
|
if (tab != null) {
|
|
4168
4181
|
selectTab(routes.tabs.indexOf(tab))
|
|
@@ -4492,6 +4505,11 @@ class MainActivity : AppCompatActivity() {
|
|
|
4492
4505
|
* screen shown before hears true (its `navigation.onAppear`).
|
|
4493
4506
|
*/
|
|
4494
4507
|
private fun show(screen: Screen) {
|
|
4508
|
+
// A retained tab may have been the offscreen preview of a cancelled
|
|
4509
|
+
// swipe, or the outgoing view of a completed swipe. Its animator can
|
|
4510
|
+
// finish after the peer animator, so clear that ownership on display.
|
|
4511
|
+
screen.view.animate().withEndAction(null).setListener(null).cancel()
|
|
4512
|
+
screen.view.translationX = 0f
|
|
4495
4513
|
// A navigation breadcrumb: the trail a crash report carries.
|
|
4496
4514
|
if (visibleScreen !== screen) CraftCrashReporter.screenAppeared(screen.route ?: "home")
|
|
4497
4515
|
val bars = chrome
|
|
@@ -4770,6 +4788,12 @@ class MainActivity : AppCompatActivity() {
|
|
|
4770
4788
|
// app script has no stack of its own; it uses NAVIGATE_OPEN.
|
|
4771
4789
|
if (visibleScreen !== screen) return
|
|
4772
4790
|
val route = payload.optString("screen").takeIf { it.isNotBlank() } ?: return
|
|
4791
|
+
// Resolve and replace in one host operation. A pop followed by
|
|
4792
|
+
// open would send the second message from a closing isolate.
|
|
4793
|
+
if (message.optString("type") == "NAVIGATE_REPLACE" && route.startsWith("/")) {
|
|
4794
|
+
navigateOpen(route, replace = true)
|
|
4795
|
+
return
|
|
4796
|
+
}
|
|
4773
4797
|
if (message.optString("type") == "NAVIGATE_REPLACE") {
|
|
4774
4798
|
val stack = screens
|
|
4775
4799
|
val removed = stack.removeAt(stack.lastIndex)
|
|
@@ -6101,7 +6125,9 @@ class MainActivity : AppCompatActivity() {
|
|
|
6101
6125
|
null
|
|
6102
6126
|
} else defaultBackground
|
|
6103
6127
|
val overflow = craftNativeOptionalString(style.opt("overflow"))
|
|
6104
|
-
|
|
6128
|
+
// A replaced image clips its own pixels to border-radius, including
|
|
6129
|
+
// when its surrounding layout permits visible overflow.
|
|
6130
|
+
view.clipToOutline = overflow == "hidden" || (view is ImageView && (radius > 0 || hasCornerRadii))
|
|
6105
6131
|
if (view is ViewGroup) view.clipChildren = overflow == "hidden"
|
|
6106
6132
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
|
|
6107
6133
|
(style.has("shadowColor") || style.has("shadowOpacity") || style.has("shadowRadius"))
|
|
@@ -6499,12 +6525,38 @@ class MainActivity : AppCompatActivity() {
|
|
|
6499
6525
|
}
|
|
6500
6526
|
imageJobs[view] = imageExecutor.submit {
|
|
6501
6527
|
val bitmap = runCatching {
|
|
6502
|
-
val
|
|
6528
|
+
val publicRequest = headers.isEmpty() && Uri.parse(uri).userInfo.isNullOrEmpty()
|
|
6529
|
+
if (publicRequest) {
|
|
6530
|
+
val bytes = imageCache.read(uri)
|
|
6531
|
+
if (bytes != null) {
|
|
6532
|
+
val saved = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
|
|
6533
|
+
if (saved != null) return@runCatching saved
|
|
6534
|
+
imageCache.remove(uri)
|
|
6535
|
+
}
|
|
6536
|
+
}
|
|
6537
|
+
val connection = (URL(uri).openConnection() as HttpURLConnection).apply {
|
|
6503
6538
|
connectTimeout = 10_000
|
|
6504
6539
|
readTimeout = 10_000
|
|
6540
|
+
// This loader owns its bounded public cache. Do not let
|
|
6541
|
+
// an application's global ResponseCache retain requests.
|
|
6542
|
+
useCaches = false
|
|
6505
6543
|
headers.forEach { (name, value) -> setRequestProperty(name, value) }
|
|
6506
6544
|
}
|
|
6507
|
-
|
|
6545
|
+
try {
|
|
6546
|
+
val downloaded = connection.inputStream.use(BitmapFactory::decodeStream)
|
|
6547
|
+
if (publicRequest) {
|
|
6548
|
+
val responseHeaders = connection.headerFields.entries.filter { it.key != null }
|
|
6549
|
+
.associate { it.key!! to it.value.orEmpty() }
|
|
6550
|
+
val policy = if (connection.responseCode == HttpURLConnection.HTTP_OK)
|
|
6551
|
+
craftNativeImagePolicy(headers, responseHeaders, System.currentTimeMillis()) else null
|
|
6552
|
+
if (downloaded != null && policy != null) {
|
|
6553
|
+
// Encode only after a complete decode, then commit
|
|
6554
|
+
// before onLoad so a cold offline reopen is safe.
|
|
6555
|
+
imageCache.write(uri, policy) { downloaded.compress(Bitmap.CompressFormat.PNG, 100, it) }
|
|
6556
|
+
} else imageCache.remove(uri)
|
|
6557
|
+
}
|
|
6558
|
+
downloaded
|
|
6559
|
+
} finally { connection.disconnect() }
|
|
6508
6560
|
}.getOrNull()
|
|
6509
6561
|
runOnUiThread {
|
|
6510
6562
|
if (imageSources[view] != requestKey) return@runOnUiThread
|