@craft-native/android 0.0.223 → 0.0.225
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
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,141 @@
|
|
|
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
|
+
finally { staging?.delete() }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fun remove(address: String) = synchronized(lock) { runCatching { file(address).delete() }; Unit }
|
|
107
|
+
|
|
108
|
+
private fun prune() {
|
|
109
|
+
val entries = root.listFiles().orEmpty().filter { entryName.matches(it.name) }.sortedBy(File::lastModified)
|
|
110
|
+
var count = entries.size
|
|
111
|
+
var bytes = entries.sumOf { (it.length() - HEADER_BYTES).coerceAtLeast(0) }
|
|
112
|
+
for (entry in entries) {
|
|
113
|
+
if (count <= maxEntries && bytes <= maxBytes) break
|
|
114
|
+
val size = (entry.length() - HEADER_BYTES).coerceAtLeast(0)
|
|
115
|
+
if (entry.delete()) { count--; bytes -= size }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private class BoundedImageOutput(private val output: OutputStream, private val limit: Long) : OutputStream() {
|
|
120
|
+
var written = 0L
|
|
121
|
+
private set
|
|
122
|
+
override fun write(value: Int) {
|
|
123
|
+
if (written >= limit) throw IOException("Cached image is too large")
|
|
124
|
+
output.write(value)
|
|
125
|
+
written++
|
|
126
|
+
}
|
|
127
|
+
override fun write(bytes: ByteArray, offset: Int, length: Int) {
|
|
128
|
+
if (written + length > limit) throw IOException("Cached image is too large")
|
|
129
|
+
output.write(bytes, offset, length)
|
|
130
|
+
written += length
|
|
131
|
+
}
|
|
132
|
+
override fun flush() = output.flush()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private companion object {
|
|
136
|
+
const val MAGIC = 0x43494d31
|
|
137
|
+
const val HEADER_BYTES = 12L
|
|
138
|
+
val entryName = Regex("^[a-f0-9]{64}\\.cache$")
|
|
139
|
+
val lock = Any()
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -92,6 +92,7 @@ import android.widget.Switch
|
|
|
92
92
|
import android.widget.TextView
|
|
93
93
|
import android.widget.MediaController
|
|
94
94
|
import android.widget.VideoView
|
|
95
|
+
import androidx.activity.OnBackPressedCallback
|
|
95
96
|
import androidx.appcompat.app.AppCompatActivity
|
|
96
97
|
import androidx.appcompat.app.AppCompatDelegate
|
|
97
98
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
@@ -121,6 +122,8 @@ import com.google.android.gms.maps.model.Polyline
|
|
|
121
122
|
import com.google.android.gms.maps.model.PolylineOptions
|
|
122
123
|
import org.json.JSONArray
|
|
123
124
|
import org.json.JSONObject
|
|
125
|
+
import java.io.File
|
|
126
|
+
import java.net.HttpURLConnection
|
|
124
127
|
import java.net.URL
|
|
125
128
|
import java.text.SimpleDateFormat
|
|
126
129
|
import java.util.Calendar
|
|
@@ -3804,6 +3807,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
3804
3807
|
private var windowSafeInsets = intArrayOf(0, 0, 0, 0)
|
|
3805
3808
|
private var useMessagePorts = false
|
|
3806
3809
|
private val imageExecutor = Executors.newFixedThreadPool(2)
|
|
3810
|
+
private val imageCache by lazy { CraftNativeImageCache(File(cacheDir, "craft-native-images")) }
|
|
3807
3811
|
/** Lays out each isolate's prelude (the synchronous stores) off the main thread. */
|
|
3808
3812
|
private val preludeExecutor = Executors.newSingleThreadExecutor()
|
|
3809
3813
|
private val imageSources = mutableMapOf<ImageView, String>()
|
|
@@ -3826,6 +3830,9 @@ class MainActivity : AppCompatActivity() {
|
|
|
3826
3830
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
3827
3831
|
applyConfiguredAppearance()
|
|
3828
3832
|
super.onCreate(savedInstanceState)
|
|
3833
|
+
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
|
3834
|
+
override fun handleOnBackPressed() = handleNativeBack()
|
|
3835
|
+
})
|
|
3829
3836
|
initialDeepLink = savedInstanceState?.getString("craft.initialDeepLink")?.let(Uri::parse) ?: intent?.data
|
|
3830
3837
|
initialDeepLinkClaimed = savedInstanceState?.getBoolean("craft.initialDeepLinkClaimed", false) ?: false
|
|
3831
3838
|
deliveredInitialDeepLink = savedInstanceState?.getBoolean("craft.deliveredInitialDeepLink", false) ?: false
|
|
@@ -3987,8 +3994,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
3987
3994
|
}
|
|
3988
3995
|
|
|
3989
3996
|
private fun onBackPressedDispatcherCompat() {
|
|
3990
|
-
|
|
3991
|
-
onBackPressed()
|
|
3997
|
+
onBackPressedDispatcher.onBackPressed()
|
|
3992
3998
|
}
|
|
3993
3999
|
|
|
3994
4000
|
private fun haptic() {
|
|
@@ -4153,13 +4159,22 @@ class MainActivity : AppCompatActivity() {
|
|
|
4153
4159
|
* page on the app's site in the in-app browser. Another site's address
|
|
4154
4160
|
* opens in the browser too.
|
|
4155
4161
|
*/
|
|
4156
|
-
private fun navigateOpen(target: String, fromLink: Boolean = false) {
|
|
4162
|
+
private fun navigateOpen(target: String, fromLink: Boolean = false, replace: Boolean = false) {
|
|
4157
4163
|
val path = routes.appPath(target, urlSchemes)
|
|
4158
4164
|
if (path == null) {
|
|
4159
4165
|
openExternal(target)
|
|
4160
4166
|
return
|
|
4161
4167
|
}
|
|
4162
4168
|
val match = routes.match(path)
|
|
4169
|
+
if (replace && match != null && screens.isNotEmpty()) {
|
|
4170
|
+
val stack = screens
|
|
4171
|
+
val removed = stack.removeAt(stack.lastIndex)
|
|
4172
|
+
val previous = stack.lastOrNull()
|
|
4173
|
+
closeScreen(removed) {
|
|
4174
|
+
if (screens === stack && stack.lastOrNull() === previous) navigateOpen(path, fromLink)
|
|
4175
|
+
}
|
|
4176
|
+
return
|
|
4177
|
+
}
|
|
4163
4178
|
val tab = routes.tabFor(path)
|
|
4164
4179
|
if (tab != null) {
|
|
4165
4180
|
selectTab(routes.tabs.indexOf(tab))
|
|
@@ -4767,6 +4782,12 @@ class MainActivity : AppCompatActivity() {
|
|
|
4767
4782
|
// app script has no stack of its own; it uses NAVIGATE_OPEN.
|
|
4768
4783
|
if (visibleScreen !== screen) return
|
|
4769
4784
|
val route = payload.optString("screen").takeIf { it.isNotBlank() } ?: return
|
|
4785
|
+
// Resolve and replace in one host operation. A pop followed by
|
|
4786
|
+
// open would send the second message from a closing isolate.
|
|
4787
|
+
if (message.optString("type") == "NAVIGATE_REPLACE" && route.startsWith("/")) {
|
|
4788
|
+
navigateOpen(route, replace = true)
|
|
4789
|
+
return
|
|
4790
|
+
}
|
|
4770
4791
|
if (message.optString("type") == "NAVIGATE_REPLACE") {
|
|
4771
4792
|
val stack = screens
|
|
4772
4793
|
val removed = stack.removeAt(stack.lastIndex)
|
|
@@ -6098,7 +6119,9 @@ class MainActivity : AppCompatActivity() {
|
|
|
6098
6119
|
null
|
|
6099
6120
|
} else defaultBackground
|
|
6100
6121
|
val overflow = craftNativeOptionalString(style.opt("overflow"))
|
|
6101
|
-
|
|
6122
|
+
// A replaced image clips its own pixels to border-radius, including
|
|
6123
|
+
// when its surrounding layout permits visible overflow.
|
|
6124
|
+
view.clipToOutline = overflow == "hidden" || (view is ImageView && (radius > 0 || hasCornerRadii))
|
|
6102
6125
|
if (view is ViewGroup) view.clipChildren = overflow == "hidden"
|
|
6103
6126
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
|
|
6104
6127
|
(style.has("shadowColor") || style.has("shadowOpacity") || style.has("shadowRadius"))
|
|
@@ -6496,12 +6519,38 @@ class MainActivity : AppCompatActivity() {
|
|
|
6496
6519
|
}
|
|
6497
6520
|
imageJobs[view] = imageExecutor.submit {
|
|
6498
6521
|
val bitmap = runCatching {
|
|
6499
|
-
val
|
|
6522
|
+
val publicRequest = headers.isEmpty() && Uri.parse(uri).userInfo.isNullOrEmpty()
|
|
6523
|
+
if (publicRequest) {
|
|
6524
|
+
val bytes = imageCache.read(uri)
|
|
6525
|
+
if (bytes != null) {
|
|
6526
|
+
val saved = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
|
|
6527
|
+
if (saved != null) return@runCatching saved
|
|
6528
|
+
imageCache.remove(uri)
|
|
6529
|
+
}
|
|
6530
|
+
}
|
|
6531
|
+
val connection = (URL(uri).openConnection() as HttpURLConnection).apply {
|
|
6500
6532
|
connectTimeout = 10_000
|
|
6501
6533
|
readTimeout = 10_000
|
|
6534
|
+
// This loader owns its bounded public cache. Do not let
|
|
6535
|
+
// an application's global ResponseCache retain requests.
|
|
6536
|
+
useCaches = false
|
|
6502
6537
|
headers.forEach { (name, value) -> setRequestProperty(name, value) }
|
|
6503
6538
|
}
|
|
6504
|
-
|
|
6539
|
+
try {
|
|
6540
|
+
val downloaded = connection.inputStream.use(BitmapFactory::decodeStream)
|
|
6541
|
+
if (publicRequest) {
|
|
6542
|
+
val responseHeaders = connection.headerFields.entries.filter { it.key != null }
|
|
6543
|
+
.associate { it.key!! to it.value.orEmpty() }
|
|
6544
|
+
val policy = if (connection.responseCode == HttpURLConnection.HTTP_OK)
|
|
6545
|
+
craftNativeImagePolicy(headers, responseHeaders, System.currentTimeMillis()) else null
|
|
6546
|
+
if (downloaded != null && policy != null) {
|
|
6547
|
+
// Encode only after a complete decode, then commit
|
|
6548
|
+
// before onLoad so a cold offline reopen is safe.
|
|
6549
|
+
imageCache.write(uri, policy) { downloaded.compress(Bitmap.CompressFormat.PNG, 100, it) }
|
|
6550
|
+
} else imageCache.remove(uri)
|
|
6551
|
+
}
|
|
6552
|
+
downloaded
|
|
6553
|
+
} finally { connection.disconnect() }
|
|
6505
6554
|
}.getOrNull()
|
|
6506
6555
|
runOnUiThread {
|
|
6507
6556
|
if (imageSources[view] != requestKey) return@runOnUiThread
|
|
@@ -7286,7 +7335,9 @@ class MainActivity : AppCompatActivity() {
|
|
|
7286
7335
|
}
|
|
7287
7336
|
|
|
7288
7337
|
@Deprecated("Uses the activity back callback for native route navigation")
|
|
7289
|
-
override fun onBackPressed()
|
|
7338
|
+
override fun onBackPressed() = handleNativeBack()
|
|
7339
|
+
|
|
7340
|
+
private fun handleNativeBack() {
|
|
7290
7341
|
val screen = screens.lastOrNull() ?: return back()
|
|
7291
7342
|
val modal = screen.modalVisibility.entries.lastOrNull { (view, visible) -> visible && view.visibility == View.VISIBLE }
|
|
7292
7343
|
if (modal != null) {
|