Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
cef277d
Skip redundant pod install for existing Hermes setting
EmmaYuan1015 Oct 21, 2026
af06ec1
Preserve underlying Expo Go download and installation errors
EmmaYuan1015 Oct 28, 2026
0451195
Stop New Architecture command when platform selection is cancelled
rksharma-owg Oct 30, 2026
faa54d3
Add unit tests for OutputVerifier
ConnorQi01 Oct 30, 2026
672fefa
Merge branch 'master' into skip-redundant-hermes-pod-install
EmmaYuan1015 Nov 4, 2026
6fd4cd4
Fix Android Hermes detection for modern Gradle configuration
ZhiyanJia Nov 4, 2026
4cdea74
Merge pull request #3057 from microsoft/fix/android-hermes-detection
ZhiyanJia Nov 4, 2026
2f9c9de
Merge branch 'master' into skip-redundant-hermes-pod-install
EmmaYuan1015 Nov 4, 2026
1bb8006
fix: support Android build variants with newer React Native CLI versions
ZhiyanJia Nov 4, 2026
4a8ca01
Merge pull request #3054 from microsoft/skip-redundant-hermes-pod-ins…
EmmaYuan1015 Nov 4, 2026
16da30c
Merge branch 'master' into fix/android-variant-cli-compatibility
ZhiyanJia Nov 4, 2026
52da710
Merge pull request #3058 from microsoft/fix/android-variant-cli-compa…
ZhiyanJia Nov 4, 2026
a162353
Merge branch 'master' into fix/3046-cancel-new-arch
ZhiyanJia Nov 4, 2026
636facf
Merge pull request #3048 from rksharma-owg/fix/3046-cancel-new-arch
ZhiyanJia Nov 4, 2026
90a63ff
Merge branch 'master' into fix-expo-go-error-details
EmmaYuan1015 Nov 5, 2026
74982ed
Scope debugger endpoint encoding assertion to the target response
EmmaYuan1015 Nov 5, 2026
e005918
Merge pull request #3060 from microsoft/fix-expo-go-error-details
EmmaYuan1015 Nov 5, 2026
b765851
Merge branch 'master' into dev/v-peq/issue-2950-outputVerifier_tests
ConnorQi01 Nov 5, 2026
50ac838
fix: use slug instead of display name in EAS project URL
ZhiyanJia Nov 5, 2026
74dc1e6
Merge pull request #3047 from microsoft/dev/v-peq/issue-2950-outputVe…
ConnorQi01 Nov 5, 2026
6a21010
Merge branch 'master' into fix/eas-project-url-slug
ZhiyanJia Nov 5, 2026
604f1e9
test: fix EAS project command tests after localization build
ZhiyanJia Nov 5, 2026
243e3f0
Merge branch 'fix/eas-project-url-slug' of https://github.com/microso…
ZhiyanJia Nov 5, 2026
bc78e64
Merge pull request #3061 from microsoft/fix/eas-project-url-slug
ZhiyanJia Nov 5, 2026
11bbf1b
Merge branch 'master' of https://github.com/microsoft/vscode-react-na…
Nov 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@ The following is a list of all the configuration properties the debugger accepts
| `expoHostType` | The connection type to be used on Expo debugging to communicate with a device or an emulator. Possible values: <ul><li>`tunnel` - allows to deploy and debug an application by means of Expo cloud services</li><li>`lan` - allows to deploy and install an application via your LAN</li><li>`local` - allows to debug an application on an emulator or an Android device without network connection</li></ul> | `string` | `lan` |
| `env` | Environment variables passed to the debugger and `react-native run-<platform>` command | `object` | `{}` |
| `envFile` | Absolute path to a file containing environment variable definitions | `string` | `${workspaceFolder}/.env` |
| `variant` | A variant to be passed to `react-native run-android`, e.g. use `devDebug` to specify `--variant=devDebug` | `string` | n/a |
| `variant` | Android build variant to run, such as `debug` or `devDebug`. The extension uses `--mode` for CLI 12 and newer, and `--variant` for older versions. Non-empty `runArguments` override this setting. | `string` | n/a |
| `scheme` | A scheme name to be passed to `react-native run-ios`, e.g. `devDebug` to specify `--scheme=devDebug` | `string` | n/a |
| `productName` | iOS bundle display name e.g. `AwesomeProject` value means that the extension will search for `AwesomeProject.app` bundle | `string` | n/a |
| `jsDebugTrace` | Enable trace collection for depended extension `vscode-js-debug` | `boolean` | n/a |
Expand Down
2 changes: 1 addition & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"reactNative.launch.runArguments.description": "Run arguments to be passed to 'react-native run-<platform>' command (override all other configuration params)",
"reactNative.launch.env.description": "Environment variables passed to the debugger and 'react-native run-<platform>' command.",
"reactNative.launch.envFile.description": "Absolute path to a file containing environment variable definitions.",
"reactNative.launch.variant.description": "A variant to be passed to 'react-native run-android', e.g. use 'devDebug' to specify '--variant=devDebug'",
"reactNative.launch.variant.description": "Android build variant to run, such as 'debug' or 'devDebug'. The extension selects '--mode' or '--variant' according to the installed CLI version.",
"reactNative.launch.scheme.description": "A scheme name to be passed to 'react-native run-ios', e.g. 'devDebug' to specify '--scheme=devDebug'",
"reactNative.launch.productName.description": "iOS bundle display name e.g. 'AwesomeProject' value means that extension will search for 'AwesomeProject.app' bundle",
"reactNative.launch.skipFiles.description": "An array of file or folder names, or glob patterns, to skip when debugging.",
Expand Down
1 change: 1 addition & 0 deletions src/common/customRequire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

interface CustomRequire extends NodeRequireFunction {
cache: any;
resolve: RequireResolve;
}

// Portion of code was taken from https://github.com/sindresorhus/ow/blob/d62a06c192b892d504887f0b97fdc842e8cbf862/source/utils/node/require.ts
Expand Down
30 changes: 26 additions & 4 deletions src/common/reactNativeProjectHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,36 @@ export class ReactNativeProjectHelper {
}

public static isAndroidHermesEnabled(projectRoot: string): boolean {
const buildGradlePath = path.join(projectRoot, "android", "app", "build.gradle");
if (!projectRoot || !fs.existsSync(buildGradlePath)) {
if (!projectRoot) {
return false;
}

const gradlePropertiesPath = path.join(projectRoot, "android", "gradle.properties");
if (fs.existsSync(gradlePropertiesPath)) {
const properties = fs.readFileSync(gradlePropertiesPath, "utf-8");
let hermesEnabled: boolean | undefined;
for (const line of properties.split(/\r?\n/)) {
const match = line.match(/^[\t ]*hermesEnabled[\t ]*[:=][\t ]*(.*)$/);
if (match) {
hermesEnabled = match[1].trim().toLowerCase() === "true";
}
}
if (hermesEnabled !== undefined) {
return hermesEnabled;
}
}

const buildGradlePath = path.join(projectRoot, "android", "app", "build.gradle");
if (!fs.existsSync(buildGradlePath)) {
return false;
}
const buildGradleContent = fs.readFileSync(buildGradlePath, "utf-8");
const hermesEnabled = /enableHermes\s*:\s*true/.test(buildGradleContent);
return hermesEnabled;
// Mask strings as well as comments so examples and comment markers in strings are ignored.
const configuration = buildGradleContent.replace(
/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\/\/[^\n\r]*|\/\*[\S\s]*?\*\//g,
" ",
);
return /\benableHermes\s*:\s*true\b/.test(configuration);
}

public static isIOSHermesEnabled(projectRoot: string): boolean {
Expand Down
68 changes: 67 additions & 1 deletion src/extension/android/androidPlatform.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.

import * as fs from "fs";
import * as path from "path";
import * as semver from "semver";
import * as nls from "vscode-nls";
import { MobilePlatformDeps, TargetType } from "../generalPlatform";
Expand All @@ -15,6 +17,7 @@ import { notNullOrUndefined } from "../../common/utils";
import { PromiseUtil } from "../../common/node/promise";
import { GeneralMobilePlatform } from "../generalMobilePlatform";
import { ProjectVersionHelper } from "../../common/projectVersionHelper";
import customRequire from "../../common/customRequire";
import { LogCatMonitorManager } from "./logCatMonitorManager";
import { AndroidTarget, AndroidTargetManager } from "./androidTargetManager";
import { AdbHelper, AndroidAPILevel } from "./adb";
Expand Down Expand Up @@ -306,7 +309,7 @@ export class AndroidPlatform extends GeneralMobilePlatform {
runArguments = this.runOptions.runArguments;
} else {
if (this.runOptions.variant) {
runArguments.push("--variant", this.runOptions.variant);
runArguments.push(this.getVariantOptionName(), this.runOptions.variant);
}
if (this.runOptions.target) {
if (
Expand All @@ -321,6 +324,69 @@ export class AndroidPlatform extends GeneralMobilePlatform {
return runArguments;
}

private getVariantOptionName(): "--mode" | "--variant" {
const searchPaths = [
this.runOptions.projectRoot,
this.runOptions.nodeModulesRoot,
path.join(this.runOptions.nodeModulesRoot, "node_modules", "react-native"),
path.join(
this.runOptions.nodeModulesRoot,
"node_modules",
"@react-native-community",
"cli",
),
];

try {
for (const packageName of [
"@react-native-community/cli-platform-android",
"@react-native-community/cli",
"react-native",
]) {
let packageJsonPath: string;
try {
packageJsonPath = customRequire.resolve(`${packageName}/package.json`, {
paths: searchPaths,
});
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "MODULE_NOT_FOUND") {
continue;
}
throw error;
}

const packageJson: unknown = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
const version =
packageJson && typeof packageJson === "object" && "version" in packageJson
? packageJson.version
: undefined;
if (typeof version !== "string" || !semver.valid(version)) {
throw new Error(`Invalid package version in ${packageJsonPath}`);
}

if (packageName === "react-native") {
// Older React Native releases bundled the Android CLI.
if (semver.lt(version, "0.60.0")) {
return "--variant";
}
break;
}

return semver.gte(version, "12.0.0") ? "--mode" : "--variant";
}

throw new Error("Cannot locate the project's Android CLI package");
} catch (error) {
throw new Error(
localize(
"CannotDetermineAndroidVariantOption",
"Cannot determine the Android build variant option: {0}. Check the project's CLI installation or specify the supported option explicitly in 'runArguments'.",
error instanceof Error ? error.message : String(error),
),
);
}
}

public dispose(): void {
if (this.logCatMonitor) {
LogCatMonitorManager.delMonitor(this.logCatMonitor.deviceId);
Expand Down
3 changes: 2 additions & 1 deletion src/extension/commands/enableHermes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class EnableHermes extends Command {
}

const podfileContent = fs.readFileSync(podfilePath, "utf-8");
const hermesMatches = podfileContent.match(/#?\s*:hermes_enabled\s*=>\s*\w*/);
const hermesMatches = podfileContent.match(/#?\s*:hermes_enabled\s*=>\s*(\w*)/);
const regex = /(use_react_native!\s*\([^)]*?)(\n\s*\))/;
const rnMatches = podfileContent.match(regex);
const nodeModulesRoot: string =
Expand All @@ -59,6 +59,7 @@ export class EnableHermes extends Command {
);

if (hermesMatches && !hermesMatches[0].startsWith("#")) {
if (hermesMatches[1] === isHermesEnabled) return;
const updatedHermes = podfileContent.replace(
/:hermes_enabled\s*=>\s*\w+/,
`:hermes_enabled => ${isHermesEnabled}`,
Expand Down
25 changes: 17 additions & 8 deletions src/extension/commands/installExpoGoApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as vscode from "vscode";
import * as nls from "vscode-nls";
import { OutputChannelLogger } from "../log/OutputChannelLogger";
import { ErrorHelper } from "../../common/error/errorHelper";
import { NestedError } from "../../common/error/internalError";
import { InternalErrorCode } from "../../common/error/internalErrorCode";
import { downloadExpoGo } from "../../common/downloadHelper";
import { installAndroidApplication, installiOSApplication } from "../../common/installHelper";
Expand Down Expand Up @@ -71,19 +72,23 @@ export class InstallExpoGoApplication extends Command {
if (!fs.existsSync(fileName)) {
try {
await downloadExpoGo(targetUrl, fileName);
} catch {
throw new Error(
} catch (error) {
throw new NestedError(
InternalErrorCode.FailedToInstallExpoGo,
localize("FailedToDownloadExpoGo", "Failed to download Expo Go."),
error,
);
}
}

if (installItem === "Auto") {
try {
await installAndroidApplication(this.project, fileName);
} catch {
throw new Error(
} catch (error) {
throw new NestedError(
InternalErrorCode.FailedToInstallExpoGo,
localize("FailedToInstallExpoGo", "Failed to install Expo Go."),
error,
);
}
} else {
Expand Down Expand Up @@ -123,19 +128,23 @@ export class InstallExpoGoApplication extends Command {
.getPackager()
.getProjectPath()}/expogo_${iOSClientVersion}.tar.gz`,
);
} catch {
throw new Error(
} catch (error) {
throw new NestedError(
InternalErrorCode.FailedToInstallExpoGo,
localize("FailedToDownloadExpoGo", "Failed to download Expo Go."),
error,
);
}
}

if (installItem === "Auto" && os.platform() === "darwin") {
try {
await installiOSApplication(this.project, tarFile);
} catch {
throw new Error(
} catch (error) {
throw new NestedError(
InternalErrorCode.FailedToInstallExpoGo,
localize("FailedToInstallExpoGo", "Failed to install Expo Go."),
error,
);
}
} else {
Expand Down
Loading
Loading