microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
43e1a99628463d630522ffd044ae583fe85de4b3

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/commandPaletteHandler.ts

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