microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2df05faacc2fb1d23abe2482b2c2fbd9475f5e64

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/commandPaletteHandler.ts

362lines · 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 "./exponent/xdlInterface";
7import {SettingsHelper} from "./settingsHelper";
8import {OutputChannelLogger} from "./log/OutputChannelLogger";
9import {Packager} from "../common/packager";
10import {TargetType, GeneralMobilePlatform} from "./generalMobilePlatform";
11import {AndroidPlatform} from "./android/androidPlatform";
12import {IOSPlatform} from "./ios/iOSPlatform";
13import {ReactNativeProjectHelper} from "../common/reactNativeProjectHelper";
14import {TargetPlatformHelper} from "../common/targetPlatformHelper";
15import {TelemetryHelper} from "../common/telemetryHelper";
16import {ExponentHelper} from "./exponent/exponentHelper";
17import {ReactDirManager} from "./reactDirManager";
18import {ExtensionServer} from "./extensionServer";
19import {IAndroidRunOptions, IIOSRunOptions} from "./launchArgs";
20import { ExponentPlatform } from "./exponent/exponentPlatform";
21
22interface IReactNativeStuff {
23 packager: Packager;
24 exponentHelper: ExponentHelper;
25 reactDirManager: ReactDirManager;
26 extensionServer: ExtensionServer;
27}
28
29interface IReactNativeProject extends IReactNativeStuff {
30 workspaceFolder: vscode.WorkspaceFolder;
31}
32
33export class CommandPaletteHandler {
34 private static projectsCache: {[key: string]: IReactNativeProject} = {};
35 private static logger: OutputChannelLogger = OutputChannelLogger.getMainChannel();
36
37 public static addFolder(workspaceFolder: vscode.WorkspaceFolder, stuff: IReactNativeStuff): void {
38 this.logger.debug(`Command palette: added folder ${workspaceFolder.uri.fsPath}`);
39 this.projectsCache[workspaceFolder.uri.fsPath] = {
40 ...stuff,
41 workspaceFolder,
42 };
43 }
44
45 public static getFolder(workspaceFolder: vscode.WorkspaceFolder): IReactNativeProject {
46 return this.projectsCache[workspaceFolder.uri.fsPath];
47 }
48
49 public static delFolder(workspaceFolder: vscode.WorkspaceFolder): void {
50 delete this.projectsCache[workspaceFolder.uri.fsPath];
51 }
52
53 /**
54 * Starts the React Native packager
55 */
56 public static startPackager(): Q.Promise<void> {
57 return this.selectProject()
58 .then((project: IReactNativeProject) => {
59 return this.executeCommandInContext("startPackager", project.workspaceFolder, () => {
60 return project.packager.isRunning()
61 .then((running) => {
62 return running ? project.packager.stop() : Q.resolve(void 0);
63 });
64 })
65 .then(() => project.packager.start());
66 });
67 }
68
69 /**
70 * Kills the React Native packager invoked by the extension's packager
71 */
72 public static stopPackager(): Q.Promise<void> {
73 return this.selectProject()
74 .then((project: IReactNativeProject) => {
75 return this.executeCommandInContext("stopPackager", project.workspaceFolder, () => project.packager.stop());
76 });
77 }
78
79 public static stopAllPackagers(): Q.Promise<void> {
80 let keys = Object.keys(this.projectsCache);
81 let promises: Q.Promise<void>[] = [];
82 keys.forEach((key) => {
83 let project = this.projectsCache[key];
84 promises.push(this.executeCommandInContext("stopPackager", project.workspaceFolder, () => project.packager.stop()));
85 });
86
87 return Q.all(promises).then(() => {});
88 }
89
90 /**
91 * Restarts the React Native packager
92 */
93 public static restartPackager(): Q.Promise<void> {
94 return this.selectProject()
95 .then((project: IReactNativeProject) => {
96 return this.executeCommandInContext("restartPackager", project.workspaceFolder, () =>
97 this.runRestartPackagerCommandAndUpdateStatus(project));
98 });
99 }
100
101 /**
102 * Execute command to publish to exponent host.
103 */
104 public static publishToExpHost(): Q.Promise<void> {
105 return this.selectProject()
106 .then((project: IReactNativeProject) => {
107 return this.executeCommandInContext("publishToExpHost", project.workspaceFolder, () => {
108 return this.executePublishToExpHost(project).then((didPublish) => {
109 if (!didPublish) {
110 CommandPaletteHandler.logger.warning("Publishing was unsuccessful. Please make sure you are logged in Exponent and your project is a valid Exponentjs project");
111 }
112 });
113 });
114 });
115 }
116
117 /**
118 * Executes the 'react-native run-android' command
119 */
120 public static runAndroid(target: TargetType = "simulator"): Q.Promise<void> {
121 return this.selectProject()
122 .then((project: IReactNativeProject) => {
123 TargetPlatformHelper.checkTargetPlatformSupport("android");
124 return this.executeCommandInContext("runAndroid", project.workspaceFolder, () => {
125 const platform = <AndroidPlatform>this.createPlatform(project, "android", AndroidPlatform, target);
126 return platform.beforeStartPackager()
127 .then(() => {
128 return platform.startPackager();
129 })
130 .then(() => {
131 return platform.runApp(/*shouldLaunchInAllDevices*/true);
132 })
133 .then(() => {
134 return platform.disableJSDebuggingMode();
135 });
136 });
137 });
138 }
139
140 /**
141 * Executes the 'react-native run-ios' command
142 */
143 public static runIos(target: TargetType = "simulator"): Q.Promise<void> {
144 return this.selectProject()
145 .then((project: IReactNativeProject) => {
146 TargetPlatformHelper.checkTargetPlatformSupport("ios");
147 return this.executeCommandInContext("runIos", project.workspaceFolder, () => {
148 const platform = <IOSPlatform>this.createPlatform(project, "ios", IOSPlatform, target);
149 return platform.beforeStartPackager()
150 .then(() => {
151 return platform.startPackager();
152 })
153 .then(() => {
154 // Set the Debugging setting to disabled, because in iOS it's persisted across runs of the app
155 return platform.disableJSDebuggingMode();
156 })
157 .catch(() => { }) // If setting the debugging mode fails, we ignore the error and we run the run ios command anyways
158 .then(() => {
159 return platform.runApp();
160 });
161 });
162 });
163 }
164
165 /**
166 * Starts the Exponent packager
167 */
168 public static runExponent(): Q.Promise<void> {
169 return this.selectProject()
170 .then((project: IReactNativeProject) => {
171 return this.loginToExponent(project)
172 .then(() => {
173 return this.executeCommandInContext("runExponent", project.workspaceFolder, () => {
174 const platform = <ExponentPlatform>this.createPlatform(project, "exponent", ExponentPlatform);
175 return platform.beforeStartPackager()
176 .then(() => {
177 return platform.startPackager();
178 })
179 .then(() => {
180 return platform.runApp();
181 });
182 });
183 });
184 });
185 }
186
187 public static showDevMenu(): Q.Promise<void> {
188 return this.selectProject()
189 .then((project: IReactNativeProject) => {
190 const androidPlatform = <AndroidPlatform>this.createPlatform(project, "android", AndroidPlatform);
191 androidPlatform.showDevMenu()
192 .catch(() => { }); // Ignore any errors
193 const iosPlatform = <IOSPlatform>this.createPlatform(project, "ios", IOSPlatform);
194 iosPlatform.showDevMenu()
195 .catch(() => { }); // Ignore any errors
196 return Q.resolve(void 0);
197 });
198 }
199
200 public static reloadApp(): Q.Promise<void> {
201 return this.selectProject()
202 .then((project: IReactNativeProject) => {
203 const androidPlatform = <AndroidPlatform>this.createPlatform(project, "android", AndroidPlatform);
204 androidPlatform.reloadApp()
205 .catch(() => { }); // Ignore any errors
206 const iosPlatform = <IOSPlatform>this.createPlatform(project, "ios", IOSPlatform);
207 iosPlatform.reloadApp()
208 .catch(() => { }); // Ignore any errors
209 return Q.resolve(void 0);
210 });
211 }
212
213 public static getPlatformByCommandName(commandName: string): string {
214 commandName = commandName.toLocaleLowerCase();
215
216 if (commandName.indexOf("android") > -1) {
217 return "android";
218 }
219
220 if (commandName.indexOf("ios") > -1) {
221 return "ios";
222 }
223
224 if (commandName.indexOf("exponent") > -1) {
225 return "exponent";
226 }
227
228 return "";
229 }
230
231 private static createPlatform(project: IReactNativeProject, platform: "ios" | "android" | "exponent", platformClass: typeof GeneralMobilePlatform, target?: TargetType): GeneralMobilePlatform {
232 const runOptions = CommandPaletteHandler.getRunOptions(project, platform, target);
233 return new platformClass(runOptions, {
234 packager: project.packager,
235 });
236 }
237
238 private static runRestartPackagerCommandAndUpdateStatus(project: IReactNativeProject): Q.Promise<void> {
239 return project.packager.restart(SettingsHelper.getPackagerPort(project.workspaceFolder.uri.fsPath));
240 }
241
242 /**
243 * Ensures that we are in a React Native project and then executes the operation
244 * Otherwise, displays an error message banner
245 * {operation} - a function that performs the expected operation
246 */
247 private static executeCommandInContext(rnCommand: string, workspaceFolder: vscode.WorkspaceFolder, operation: () => Q.Promise<void>): Q.Promise<void> {
248 const extProps = {
249 platform: {
250 value: CommandPaletteHandler.getPlatformByCommandName(rnCommand),
251 isPii: false,
252 },
253 };
254
255 return TelemetryHelper.generate("RNCommand", extProps, (generator) => {
256 generator.add("command", rnCommand, false);
257 const projectRoot = SettingsHelper.getReactNativeProjectRoot(workspaceFolder.uri.fsPath);
258 this.logger.debug(`Command palette: run project ${projectRoot} in context`);
259 return ReactNativeProjectHelper.isReactNativeProject(projectRoot)
260 .then(isRNProject => {
261 generator.add("isRNProject", isRNProject, false);
262 if (isRNProject) {
263 // Bring the log channel to focus
264 this.logger.setFocusOnLogChannel();
265
266 // Execute the operation
267 return operation();
268 } else {
269 vscode.window.showErrorMessage(`${projectRoot} workspace is not a React Native project.`);
270 return;
271 }
272 });
273 });
274 }
275
276 /**
277 * 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.
278 */
279 private static executePublishToExpHost(project: IReactNativeProject): Q.Promise<boolean> {
280 CommandPaletteHandler.logger.info("Publishing app to Exponent server. This might take a moment.");
281 return this.loginToExponent(project)
282 .then(user => {
283 CommandPaletteHandler.logger.debug(`Publishing as ${user.username}...`);
284 return this.runExponent()
285 .then(() =>
286 XDL.publish(project.workspaceFolder.uri.fsPath))
287 .then(response => {
288 if (response.err || !response.url) {
289 return false;
290 }
291 const publishedOutput = `App successfully published to ${response.url}`;
292 CommandPaletteHandler.logger.info(publishedOutput);
293 vscode.window.showInformationMessage(publishedOutput);
294 return true;
295 });
296 });
297 }
298
299 private static loginToExponent(project: IReactNativeProject): Q.Promise<XDL.IUser> {
300 return project.exponentHelper.loginToExponent(
301 (message, password) => {
302 return Q.Promise((resolve, reject) => {
303 vscode.window.showInputBox({ placeHolder: message, password: password })
304 .then(login => {
305 resolve(login || "");
306 }, reject);
307 });
308 },
309 (message) => {
310 return Q.Promise((resolve, reject) => {
311 vscode.window.showInformationMessage(message)
312 .then(password => {
313 resolve(password || "");
314 }, reject);
315 });
316 }
317 )
318 .catch((err) => {
319 CommandPaletteHandler.logger.warning("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.");
320 throw err;
321 });
322 }
323
324 private static selectProject(): Q.Promise<IReactNativeProject> {
325 let keys = Object.keys(this.projectsCache);
326 if (keys.length > 1) {
327 return Q.Promise((resolve, reject) => {
328 vscode.window.showQuickPick(keys)
329 .then((selected) => {
330 if (selected) {
331 this.logger.debug(`Command palette: selected project ${selected}`);
332 resolve(this.projectsCache[selected]);
333 }
334 }, reject);
335 });
336 } else if (keys.length === 1) {
337 this.logger.debug(`Command palette: once project ${keys[0]}`);
338 return Q.resolve(this.projectsCache[keys[0]]);
339 } else {
340 return Q.reject(new Error("Current workspace does not contain React Native projects."));
341 }
342 }
343
344 private static getRunOptions(project: IReactNativeProject, platform: "ios" | "android" | "exponent", target: TargetType = "simulator"): IAndroidRunOptions | IIOSRunOptions {
345 const packagerPort = SettingsHelper.getPackagerPort(project.workspaceFolder.uri.fsPath);
346 const runArgs = SettingsHelper.getRunArgs(platform, target, project.workspaceFolder.uri);
347 const envArgs = SettingsHelper.getEnvArgs(platform, target, project.workspaceFolder.uri);
348 const envFile = SettingsHelper.getEnvFile(platform, target, project.workspaceFolder.uri);
349 const projectRoot = SettingsHelper.getReactNativeProjectRoot(project.workspaceFolder.uri.fsPath);
350 const runOptions: IAndroidRunOptions | IIOSRunOptions = {
351 platform: platform,
352 workspaceRoot: project.workspaceFolder.uri.fsPath,
353 projectRoot: projectRoot,
354 packagerPort: packagerPort,
355 runArguments: runArgs,
356 env: envArgs,
357 envFile: envFile,
358 };
359
360 return runOptions;
361 }
362}