From 226227ecc1c4cc442d459a779364f2ca12446713 Mon Sep 17 00:00:00 2001 From: "wizzoapp[bot]" <254688279+wizzoapp[bot]@users.noreply.github.com> Date: Fri, 3 Jul 2026 10:24:27 +0100 Subject: [PATCH] Fix Android prerelease APK generation --- apps/mobile/app.config.ts | 19 +++--- .../plugins/withAndroidGradleMemory.cjs | 33 +++++++++++ .../withAndroidWidgetStringResources.cjs | 58 +++++++++++++++++++ 3 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 apps/mobile/plugins/withAndroidGradleMemory.cjs create mode 100644 apps/mobile/plugins/withAndroidWidgetStringResources.cjs diff --git a/apps/mobile/app.config.ts b/apps/mobile/app.config.ts index a1b50470f54..b72de61194c 100644 --- a/apps/mobile/app.config.ts +++ b/apps/mobile/app.config.ts @@ -68,6 +68,14 @@ const mobileAppVersion = repoEnv.EXPO_PUBLIC_APP_VERSION, readReleaseVersion(), ) ?? "0.1.0"; +const widgets = [ + { + name: "AgentActivity", + displayName: "Agent Activity", + description: "Shows the current state of active T3 Code agents.", + supportedFamilies: ["systemSmall", "systemMedium", "accessoryRectangular"], + }, +] as const; function readReleaseVersion(): string | undefined { const releaseVersionPath = NodePath.resolve(process.cwd(), "release-version.json"); @@ -177,17 +185,12 @@ const config: ExpoConfig = { bundleIdentifier: `${variant.iosBundleIdentifier}.widgets`, groupIdentifier: `group.${variant.iosBundleIdentifier}`, enablePushNotifications: true, - widgets: [ - { - name: "AgentActivity", - displayName: "Agent Activity", - description: "Shows the current state of active T3 Code agents.", - supportedFamilies: ["systemSmall", "systemMedium", "accessoryRectangular"], - }, - ], + widgets, }, ], "./plugins/withAndroidCleartextTraffic.cjs", + "./plugins/withAndroidGradleMemory.cjs", + ["./plugins/withAndroidWidgetStringResources.cjs", { widgets }], ], extra: { appVariant: APP_VARIANT, diff --git a/apps/mobile/plugins/withAndroidGradleMemory.cjs b/apps/mobile/plugins/withAndroidGradleMemory.cjs new file mode 100644 index 00000000000..2c383eb18fc --- /dev/null +++ b/apps/mobile/plugins/withAndroidGradleMemory.cjs @@ -0,0 +1,33 @@ +const { withGradleProperties } = require("expo/config-plugins"); + +const DEFAULT_GRADLE_JVM_ARGS = + "-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:+HeapDumpOnOutOfMemoryError"; + +module.exports = function withAndroidGradleMemory(config, options = {}) { + const jvmArgs = + typeof options.jvmArgs === "string" && options.jvmArgs.trim().length > 0 + ? options.jvmArgs.trim() + : DEFAULT_GRADLE_JVM_ARGS; + + return withGradleProperties(config, (nextConfig) => { + upsertGradleProperty(nextConfig.modResults, "org.gradle.jvmargs", jvmArgs); + return nextConfig; + }); +}; + +function upsertGradleProperty(properties, key, value) { + const existing = properties.find( + (property) => property.type === "property" && property.key === key, + ); + + if (existing != null) { + existing.value = value; + return; + } + + properties.push({ + type: "property", + key, + value, + }); +} diff --git a/apps/mobile/plugins/withAndroidWidgetStringResources.cjs b/apps/mobile/plugins/withAndroidWidgetStringResources.cjs new file mode 100644 index 00000000000..0d8555070c3 --- /dev/null +++ b/apps/mobile/plugins/withAndroidWidgetStringResources.cjs @@ -0,0 +1,58 @@ +const { AndroidConfig, withStringsXml } = require("expo/config-plugins"); + +module.exports = function withAndroidWidgetStringResources(config, options = {}) { + const widgets = Array.isArray(options.widgets) ? options.widgets : []; + + return withStringsXml(config, (nextConfig) => { + const stringItems = widgets.flatMap((widget) => getWidgetStringItems(widget)); + + if (stringItems.length > 0) { + nextConfig.modResults = AndroidConfig.Strings.setStringItem( + stringItems, + nextConfig.modResults, + ); + } + + return nextConfig; + }); +}; + +function getWidgetStringItems(widget) { + const name = readNonEmptyString(widget, "name"); + const displayName = readNonEmptyString(widget, "displayName"); + const description = readNonEmptyString(widget, "description"); + const resourceName = toAndroidResourceName(name); + + return [ + AndroidConfig.Resources.buildResourceItem({ + name: `${resourceName}_display_name`, + value: displayName, + }), + AndroidConfig.Resources.buildResourceItem({ + name: `${resourceName}_description`, + value: description, + }), + ]; +} + +function readNonEmptyString(widget, key) { + const value = widget?.[key]; + + if (typeof value === "string" && value.trim().length > 0) { + return value.trim(); + } + + throw new Error(`Android widget ${key} must be a non-empty string.`); +} + +function toAndroidResourceName(name) { + const resourceName = name + .replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2") + .replace(/([a-z0-9])([A-Z])/g, "$1_$2") + .replace(/[^A-Za-z0-9_]/g, "_") + .replace(/_+/g, "_") + .replace(/^_+|_+$/g, "") + .toLowerCase(); + + return /^[a-z]/.test(resourceName) ? resourceName : `widget_${resourceName}`; +}