Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command to download application and install in simulator #2064

Merged
merged 7 commits into from Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/common/downloadHelper.ts
@@ -1,5 +1,6 @@
// 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 https from "https";
import * as vscode from "vscode";
Expand Down Expand Up @@ -36,12 +37,14 @@ export async function downloadFile(url: any, targetFile: any) {

file.on("finish", async () => {
file.close();
logger.logStream(`Download Expo Go Completed: ${targetFile as string}`);
logger.logStream(`Download Expo Go Completed: ${targetFile as string} \n`);
void vscode.window.showInformationMessage("Download Expo Go Completed.");
});

response.on("end", function () {
console.log("Progress end.");
resolve(() => {
console.log("Progress end.");
});
});
})
.on("error", error => {
Expand Down
88 changes: 88 additions & 0 deletions src/common/installHelper.ts
@@ -0,0 +1,88 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.

import { AdbHelper } from "../extension/android/adb";
import { AppLauncher } from "../extension/appLauncher";
import { SimctrlHelper } from "../extension/ios/simctl";
import { OutputChannelLogger } from "../extension/log/OutputChannelLogger";
import { ChildProcess } from "./node/childProcess";

export async function installAndroidApplication(project: AppLauncher, appPath: string) {
const logger = OutputChannelLogger.getMainChannel();
const adbHelper = new AdbHelper(
project.getPackager().getProjectPath(),
project.getOrUpdateNodeModulesRoot(),
);

const targets = await adbHelper.getOnlineTargets();
if (targets.length == 0) {
throw new Error("No online target found, please check your emulator status.");
} else if (targets.length > 1) {
logger.logStream(
`Found ${targets.length} online emulators, installing application on ${targets[0].id}. \n`,
);
try {
await adbHelper.installApplicationToEmulator(appPath);
} catch {
throw new Error(`Failed to install application: ${appPath}.`);
}
} else {
logger.logStream(`Installing application on ${targets[0].id}. \n`);
try {
await adbHelper.installApplicationToEmulator(appPath);
} catch {
throw new Error(`Failed to install application: ${appPath}.`);
}
logger.logStream(`Install Android application is completed. \n`);
}
}

export async function installiOSApplication(project: AppLauncher, appPath: string) {
const logger = OutputChannelLogger.getMainChannel();
const childProcess: ChildProcess = new ChildProcess();

const targets = await SimctrlHelper.getBootediOSSimulatorList();

try {
// Create dir to iOS app
await childProcess.execToString(
`mkdir ${project.getPackager().getProjectPath()}/expoApp.app`,
);

// Unpack .tar.gz file
await childProcess.execToString(
`tar -xf ${appPath} -C ${project.getPackager().getProjectPath()}/expoApp.app`,
);
} catch (e) {
throw e;
}

if (targets.length == 1) {
throw new Error("No booted iOS simulator found, please check your simulator status.");
} else if (targets.length > 2) {
logger.logStream(
`Found ${targets.length - 1} booted simulators, installing application on ${
targets[0]
}. \n`,
);
try {
await SimctrlHelper.installApplicationToSimulator(
targets[0],
`${project.getPackager().getProjectPath()}/expoApp.app`,
);
} catch {
throw new Error(`Failed to install application: ${appPath}.`);
}
} else {
logger.logStream(`Installing application on ${targets[0]}. \n`);
try {
await SimctrlHelper.installApplicationToSimulator(
targets[0],
`${project.getPackager().getProjectPath()}/expoApp.app`,
);
} catch {
throw new Error(`Failed to install application: ${appPath}.`);
}
logger.logStream(`Install iOS application is completed. \n`);
}
}
4 changes: 4 additions & 0 deletions src/extension/android/adb.ts
Expand Up @@ -256,6 +256,10 @@ export class AdbHelper {
return this.executeQuery(deviceId, `shell "${command}"`);
}

public installApplicationToEmulator(appPath: string): Promise<string> {
return this.childProcess.execToString(`adb install ${appPath}`);
}

public executeQuery(deviceId: string, command: string): Promise<string> {
return this.childProcess.execToString(this.generateCommandForTarget(deviceId, command));
}
Expand Down
88 changes: 67 additions & 21 deletions src/extension/commands/installExpoGoApplication.ts
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for details.

import * as assert from "assert";
import * as fs from "fs";
import * as https from "https";
import * as os from "os";
import * as vscode from "vscode";
Expand All @@ -10,7 +11,7 @@ import { OutputChannelLogger } from "../log/OutputChannelLogger";
import { ErrorHelper } from "../../common/error/errorHelper";
import { InternalErrorCode } from "../../common/error/internalErrorCode";
import { downloadExpoGo } from "../../common/downloadHelper";
import { getTimestamp } from "../../common/utils";
import { installAndroidApplication, installiOSApplication } from "../../common/installHelper";
import { Command } from "./util/command";

nls.config({
Expand All @@ -30,6 +31,9 @@ export class InstallExpoGoApplication extends Command {
const item = await vscode.window.showQuickPick(["Android", "iOS"], {
placeHolder: "Select type for mobile OS",
});
const installItem = await vscode.window.showQuickPick(["Manual", "Auto"], {
placeHolder: "How to install application",
});
const expoHelper = this.project.getExponentHelper();
logger.info(localize("CheckExpoEnvironment", "Checking Expo project environment."));
const isExpo = await expoHelper.isExpoManagedApp(true);
Expand All @@ -50,16 +54,34 @@ export class InstallExpoGoApplication extends Command {

const targetUrl = expoUrlInfo.androidClientUrl;
const androidClientVersion = expoUrlInfo.androidClientVersion as string;
try {
await downloadExpoGo(
targetUrl,
`${this.project
.getPackager()
.getProjectPath()}/expogo_${androidClientVersion}_${getTimestamp()}.apk`,
);
} catch {
throw new Error(
localize("FailedToDownloadExpoGo", "Failed to download Expo Go."),
const fileName = `${this.project
.getPackager()
.getProjectPath()}/expogo_${androidClientVersion}.apk`;

if (!fs.existsSync(fileName)) {
try {
await downloadExpoGo(targetUrl, fileName);
} catch {
throw new Error(
localize("FailedToDownloadExpoGo", "Failed to download Expo Go."),
);
}
}

if (installItem == "Auto") {
try {
await installAndroidApplication(this.project, fileName);
} catch {
throw new Error(
localize("FailedToInstallExpoGo", "Failed to install Expo Go."),
);
}
} else {
logger.logStream(
localize(
"ManualInstall",
"Please manually install Expo Go from project root path. \n",
),
);
}
} else if (item == "iOS") {
Expand All @@ -78,16 +100,40 @@ export class InstallExpoGoApplication extends Command {

const targetUrl = expoUrlInfo.iosClientUrl;
const iOSClientVersion = expoUrlInfo.iosClientVersion as string;
try {
await downloadExpoGo(
targetUrl,
`${this.project
.getPackager()
.getProjectPath()}/expogo_${iOSClientVersion}_${getTimestamp()}.tar.gz`,
);
} catch {
throw new Error(
localize("FailedToDownloadExpoGo", "Failed to download Expo Go."),

const tarFile = `${this.project
.getPackager()
.getProjectPath()}/expogo_${iOSClientVersion}.tar.gz`;

if (!fs.existsSync(tarFile)) {
try {
await downloadExpoGo(
targetUrl,
`${this.project
.getPackager()
.getProjectPath()}/expogo_${iOSClientVersion}.tar.gz`,
);
} catch {
throw new Error(
localize("FailedToDownloadExpoGo", "Failed to download Expo Go."),
);
}
}

if (installItem == "Auto" && os.platform() == "darwin") {
try {
await installiOSApplication(this.project, tarFile);
} catch {
throw new Error(
localize("FailedToInstallExpoGo", "Failed to install Expo Go."),
);
}
} else {
logger.logStream(
localize(
"CannotAutoInstall",
"Cannot auto install Expo Go, selected manual install or target machine is not MacOS. \n",
),
);
}
} else {
Expand Down
25 changes: 25 additions & 0 deletions src/extension/ios/simctl.ts
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.

import { ChildProcess } from "../../common/node/childProcess";

const childProcess: ChildProcess = new ChildProcess();

export class SimctrlHelper {
public static async getBootediOSSimulatorList(): Promise<string[]> {
const getBootedSimulatorCommand =
"xcrun simctl list | awk -F'[()]' '/(Booted)/ { print $2 }'";

const targetResult = await childProcess.execToString(getBootedSimulatorCommand);
const targetList = targetResult.split("\n");
return targetList;
}

public static async installApplicationToSimulator(
targetId: string,
appPath: string,
): Promise<void> {
const installCommand = `xcrun simctl install ${targetId} ${appPath}`;
await childProcess.execToString(installCommand);
}
}
3 changes: 1 addition & 2 deletions src/extension/settingsHelper.ts
Expand Up @@ -216,9 +216,8 @@ export class SettingsHelper {
if (workspaceSettingsContent.settings) {
const exclude = workspaceSettingsContent.settings["react-native.workspace.exclude"];
return exclude ? exclude : [];
} else {
return [];
}
return [];
}
return [];
}
Expand Down