microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
8abbd163d4bed90e01798baa8aab300f3374a8e9

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/extension/commandPaletteHandler.ts

228lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import * as vscode from "vscode";
5import * as Q from "q";
6import * as XDL from "../common/exponent/xdlInterface";
7import {CommandExecutor} from "../common/commandExecutor";
8import {SettingsHelper} from "./settingsHelper";
9import {Log} from "../common/log/log";
10import {Packager, PackagerRunAs} from "../common/packager";
11import {AndroidPlatform} from "../common/android/androidPlatform";
12import {PackagerStatus, PackagerStatusIndicator} from "./packagerStatusIndicator";
13import {ReactNativeProjectHelper} from "../common/reactNativeProjectHelper";
14import {TargetPlatformHelper} from "../common/targetPlatformHelper";
15import {TelemetryHelper} from "../common/telemetryHelper";
16import {IOSDebugModeManager} from "../common/ios/iOSDebugModeManager";
17import {ExponentHelper} from "../common/exponent/exponentHelper";
18
19export class CommandPaletteHandler {
20 private reactNativePackager: Packager;
21 private reactNativePackageStatusIndicator: PackagerStatusIndicator;
22 private workspaceRoot: string;
23 private exponentHelper: ExponentHelper;
24
25 constructor(workspaceRoot: string, reactNativePackager: Packager, packagerStatusIndicator: PackagerStatusIndicator, exponentHelper: ExponentHelper) {
26 this.workspaceRoot = workspaceRoot;
27 this.reactNativePackager = reactNativePackager;
28 this.reactNativePackageStatusIndicator = packagerStatusIndicator;
29 this.exponentHelper = exponentHelper;
30 }
31
32 /**
33 * Starts the React Native packager
34 */
35 public startPackager(): Q.Promise<void> {
36 return this.executeCommandInContext("startPackager", () =>
37 this.reactNativePackager.isRunning()
38 .then((running) => {
39 return running ? this.reactNativePackager.stop() : Q.resolve(void 0);
40 })
41 )
42 .then(() => this.runStartPackagerCommandAndUpdateStatus());
43 }
44
45 /**
46 * Starts the Exponent packager
47 */
48 public startExponentPackager(): Q.Promise<void> {
49 return this.executeCommandInContext("startExponentPackager", () =>
50 this.reactNativePackager.isRunning()
51 .then((running) => {
52 return running ? this.reactNativePackager.stop() : Q.resolve(void 0);
53 })
54 ).then(() =>
55 this.exponentHelper.configureExponentEnvironment()
56 ).then(() => this.runStartPackagerCommandAndUpdateStatus(PackagerRunAs.EXPONENT));
57 }
58
59 /**
60 * Kills the React Native packager invoked by the extension's packager
61 */
62 public stopPackager(): Q.Promise<void> {
63 return this.executeCommandInContext("stopPackager", () => this.reactNativePackager.stop())
64 .then(() => this.reactNativePackageStatusIndicator.updatePackagerStatus(PackagerStatus.PACKAGER_STOPPED));
65 }
66
67 /**
68 * Restarts the React Native packager
69 */
70 public restartPackager(): Q.Promise<void> {
71 return this.executeCommandInContext("restartPackager", () =>
72 this.runRestartPackagerCommandAndUpdateStatus());
73 }
74
75 /**
76 * Execute command to publish to exponent host.
77 */
78 public publishToExpHost(): Q.Promise<void> {
79 return this.executeCommandInContext("publishToExpHost", () => {
80 this.executePublishToExpHost().then((didPublish) => {
81 if (!didPublish) {
82 Log.logMessage("Publishing was unsuccessful. Please make sure you are logged in Exponent and your project is a valid Exponentjs project");
83 }
84 });
85 });
86 }
87
88 /**
89 * Executes the 'react-native run-android' command
90 */
91 public runAndroid(targetType: string = "simulator"): Q.Promise<void> {
92 TargetPlatformHelper.checkTargetPlatformSupport("android");
93 const targetName = SettingsHelper.getApplicationTarget("android", targetType);
94 return this.executeCommandInContext("runAndroid", () => this.executeWithPackagerRunning(() => {
95 const packagerPort = SettingsHelper.getPackagerPort();
96 return new AndroidPlatform({ platform: "android", projectRoot: this.workspaceRoot, packagerPort: packagerPort, target: targetName }).runApp(/*shouldLaunchInAllDevices*/true);
97 }));
98 }
99
100 /**
101 * Executes the 'react-native run-ios' command
102 */
103 public runIos(targetType: string = "simulator"): Q.Promise<void> {
104 TargetPlatformHelper.checkTargetPlatformSupport("ios");
105 return this.executeCommandInContext("runIos", () => {
106 const targetName = SettingsHelper.getApplicationTarget("ios", targetType);
107 // Set the Debugging setting to disabled, because in iOS it's persisted across runs of the app
108 return new IOSDebugModeManager(this.workspaceRoot)
109 .setSimulatorRemoteDebuggingSetting(/*enable=*/ false)
110 .catch(() => { }) // If setting the debugging mode fails, we ignore the error and we run the run ios command anyways
111 .then(() => this.executeReactNativeRunCommand("run-ios", [`--${targetType}`, targetName]));
112 });
113 }
114
115 private runRestartPackagerCommandAndUpdateStatus(): Q.Promise<void> {
116 return this.reactNativePackager.restart(SettingsHelper.getPackagerPort())
117 .then(() => this.reactNativePackageStatusIndicator.updatePackagerStatus(PackagerStatus.PACKAGER_STARTED));
118 }
119
120 /**
121 * Helper method to run packager and update appropriate configurations
122 */
123 private runStartPackagerCommandAndUpdateStatus(startAs: PackagerRunAs = PackagerRunAs.REACT_NATIVE): Q.Promise<any> {
124 if (startAs === PackagerRunAs.EXPONENT) {
125 return this.loginToExponent()
126 .then(() =>
127 this.reactNativePackager.startAsExponent()
128 ).then(exponentUrl => {
129 this.reactNativePackageStatusIndicator.updatePackagerStatus(PackagerStatus.EXPONENT_PACKAGER_STARTED);
130 Log.logMessage("Application is running on Exponent.");
131 const exponentOutput = `Open your exponent app at ${exponentUrl}`;
132 Log.logMessage(exponentOutput);
133 vscode.commands.executeCommand("vscode.previewHtml", vscode.Uri.parse(exponentUrl), 1, "Expo QR code");
134 });
135 }
136 return this.reactNativePackager.startAsReactNative()
137 .then(() => this.reactNativePackageStatusIndicator.updatePackagerStatus(PackagerStatus.PACKAGER_STARTED));
138 }
139
140 /**
141 * Executes a react-native command passed after starting the packager
142 * {command} The command to be executed
143 * {args} The arguments to be passed to the command
144 */
145 private executeReactNativeRunCommand(command: string, args: string[] = []): Q.Promise<void> {
146 return this.executeWithPackagerRunning(() => {
147 return new CommandExecutor(this.workspaceRoot)
148 .spawnReactCommand(command, args).outcome;
149 });
150 }
151
152 /**
153 * Executes a lambda function after starting the packager
154 * {lambda} The lambda function to be executed
155 */
156 private executeWithPackagerRunning(lambda: () => Q.Promise<void>): Q.Promise<void> {
157 // Start the packager before executing the React-Native command
158 Log.logMessage("Attempting to start the React Native packager");
159 return this.runStartPackagerCommandAndUpdateStatus().then(lambda);
160 }
161
162 /**
163 * Ensures that we are in a React Native project and then executes the operation
164 * Otherwise, displays an error message banner
165 * {operation} - a function that performs the expected operation
166 */
167 private executeCommandInContext(rnCommand: string, operation: () => Q.Promise<void> | void): Q.Promise<void> {
168 let reactNativeProjectHelper = new ReactNativeProjectHelper(this.workspaceRoot);
169 return TelemetryHelper.generate("RNCommand", (generator) => {
170 generator.add("command", rnCommand, false);
171 return reactNativeProjectHelper.isReactNativeProject().then(isRNProject => {
172 generator.add("isRNProject", isRNProject, false);
173 if (isRNProject) {
174 // Bring the log channel to focus
175 Log.setFocusOnLogChannel();
176
177 // Execute the operation
178 return operation();
179 } else {
180 vscode.window.showErrorMessage("Current workspace is not a React Native project.");
181 }
182 });
183 });
184 }
185
186 /**
187 * Publish project to exponent server. In order to do this we need to make sure the user is logged in exponent and the packager is running.
188 */
189 private executePublishToExpHost(): Q.Promise<boolean> {
190 Log.logMessage("Publishing app to Exponent server. This might take a moment.");
191 return this.loginToExponent()
192 .then(user => {
193 Log.logMessage(`Publishing as ${user.username}...`);
194 return this.startExponentPackager()
195 .then(() =>
196 XDL.publish(this.workspaceRoot))
197 .then(response => {
198 if (response.err || !response.url) {
199 return false;
200 }
201 const publishedOutput = `App successfully published to ${response.url}`;
202 Log.logMessage(publishedOutput);
203 vscode.window.showInformationMessage(publishedOutput);
204 return true;
205 });
206 }).catch(() => {
207 Log.logWarning("An error has occured. Please make sure you are logged in to exponent, your project is setup correctly for publishing and your packager is running as exponent.");
208 return false;
209 });
210 }
211
212 private loginToExponent(): Q.Promise<XDL.IUser> {
213 return this.exponentHelper.loginToExponent(
214 (message, password) => {
215 return Q.Promise((resolve, reject) => {
216 vscode.window.showInputBox({ placeHolder: message, password: password })
217 .then(resolve, reject);
218 });
219 },
220 (message) => {
221 return Q.Promise((resolve, reject) => {
222 vscode.window.showInformationMessage(message)
223 .then(resolve, reject);
224 });
225 }
226 );
227 }
228}
229