Repository: expo/expo
File: packages/@expo/cli/src/run/android/resolveGradlePropsAsync.ts (TypeScript)

Code before:
}

export async function resolveGradlePropsAsync(
  projectRoot: string,
  options: { variant?: string; allArch?: boolean },
  device: Device
): Promise<GradleProps> {
  const variant = assertVariant(options.variant);
  // NOTE(EvanBacon): Why would this be different? Can we get the different name?
  const appName = 'app';

  const apkDirectory = path.join(projectRoot, 'android', appName, 'build', 'outputs', 'apk');

  // buildDeveloperTrust -> buildtype: trust, flavors: build, developer
  // developmentDebug -> buildType: debug, flavors: development
  // productionRelease -> buildType: release, flavors: production
  // previewDebugOptimized -> buildType: debugOptimized, flavors: preview
  // This won't work for non-standard flavor names like "myFlavor" would be treated as "my", "flavor".
  let parts = variant.split(/(?=[A-Z])/).map((v) => v.toLowerCase());

  // Special case: merge 'debug' and 'optimized' into 'debugOptimized'
  if (parts.length >= 2 && parts[parts.length - 1] === 'optimized' && parts[parts.length - 2] === 'debug') {
    parts = [...parts.slice(0, -2), 'debugOptimized'];
  }

  const flavors = parts.slice(0, -1);
  const buildType = parts[parts.length - 1] ?? 'debug';

  const apkVariantDirectory = path.join(apkDirectory, ...flavors, buildType);
  const architectures = await getConnectedDeviceABIS(buildType, device, options.allArch);

  return {
    appName,
    buildType,
    flavors,
    apkVariantDirectory,
    architectures,
  };
}

async function getConnectedDeviceABIS(
  buildType: string,
  device: Device,
  allArch?: boolean
): Promise<string> {
  // Follow the same behavior as iOS, only enable this for debug builds
  // Support both 'debug' and 'debugOptimized' build types
  const isDebugBuild = buildType === 'debug' || buildType === 'debugOptimized';
  if (allArch || !isDebugBuild) {
    return '';
  }

Review comment (bug):
(see other comment first)

If we drop `map((v) => v.toLowerCase())` and keep the previous structure intact, then this can be kept more similar:

```js
let buildType = flavors.pop() ?? 'debug';
if (flavors.length > 0 && buildType === 'Optimized') {
  buildType = flavors.pop().toLowerCase() + buildType;
} else {
  buildType = buildType.toLowerCase();
}
```

Combining the follow up with the other comment, this becomes:

```js
const flavorPath = flavors.length > 0
  ? [flavors[0].toLowerCase() + flavors.slice(1).join('')]
  : [];
```

Where finally `...flavors` could be swapped out

```diff
-  const apkVariantDirectory = path.join(apkDirectory, ...flavors, buildType);
+  const apkVariantDirectory = path.join(apkDirectory, ...flavorPath, buildType);
```

Code after:
}

export async function resolveGradlePropsAsync(
  projectRoot: string,
  options: { variant?: string; allArch?: boolean },
  device: Device
): Promise<GradleProps> {
  const variant = assertVariant(options.variant);
  // NOTE(EvanBacon): Why would this be different? Can we get the different name?
  const appName = 'app';

  const apkDirectory = path.join(projectRoot, 'android', appName, 'build', 'outputs', 'apk');

  // buildDeveloperTrust -> buildtype: trust, flavors: buildDeveloper
  // developmentDebug -> buildType: debug, flavors: development
  // productionRelease -> buildType: release, flavors: production
  // previewDebugOptimized -> buildType: debugOptimized, flavors: preview
  const parts = variant.split(/(?=[A-Z])/);

  // Special case: merge 'Optimized' suffix with preceding part, e.g. into 'debugOptimized'
  let buildType = parts.pop() ?? 'debug';
  if (parts.length > 0 && buildType === 'Optimized') {
    buildType = parts.pop()!.toLowerCase() + buildType;
  } else {
    buildType = buildType.toLowerCase();
  }

  let apkVariantDirectory: string;
  if (parts.length > 0) {
    const flavorPath = parts[0].toLowerCase() + parts.slice(1).join('');
    apkVariantDirectory = path.join(apkDirectory, flavorPath, buildType);
  } else {
    apkVariantDirectory = path.join(apkDirectory, buildType);
  }

  return {
    appName,
    buildType,
    flavors: parts.map((v) => v.toLowerCase()),
    apkVariantDirectory,
    architectures: await getConnectedDeviceABIS(buildType, device, options.allArch),
  };
}

async function getConnectedDeviceABIS(
  buildType: string,
  device: Device,
  allArch?: boolean
): Promise<string> {
  // Follow the same behavior as iOS, only enable this for debug builds
  // Support both 'debug' and 'debugOptimized' build types