microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
ef14e11b4dec66f85f409f65f431d7e9549c601d

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/commandPaletteHandler.ts

416lines · 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, PackagerRunAs} from "../common/packager";
10import {AndroidPlatform} from "./android/androidPlatform";
11import {IOSPlatform} from "./ios/iOSPlatform";
12import {PackagerStatus} from "./packagerStatusIndicator";
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 } from "./launchArgs";
20import { AppCenterCommandPalleteHandler } from "./appcenter/appCenterCommandPalleteHandler";
21import { AppCenterCommandType } from "./appcenter/appCenterConstants";
22import { AppCenterExtensionManager } from "./appcenter/appCenterExtensionManager";
23
24interface IReactNativeStuff {
25 packager: Packager;
26 exponentHelper: ExponentHelper;
27 reactDirManager: ReactDirManager;
28 extensionServer: ExtensionServer;
29 appCenterManager: AppCenterExtensionManager; // Actually not an RN Stuff, but this is RN only extension so no other than RN stuff could exist at all, yeah?
30}
31
32interface IReactNativeProject extends IReactNativeStuff {
33 workspaceFolder: vscode.WorkspaceFolder;
34}
35
36export class CommandPaletteHandler {
37 private static projectsCache: {[key: string]: IReactNativeProject} = {};
38 private static logger: OutputChannelLogger = OutputChannelLogger.getMainChannel();
39
40 // Use this class to further cmd exec delegation and not to pollute this class with AppCenter logic
41 private static appCenterCommandPalleteHandler: AppCenterCommandPalleteHandler;
42
43 public static addFolder(workspaceFolder: vscode.WorkspaceFolder, stuff: IReactNativeStuff): void {
44 this.projectsCache[workspaceFolder.uri.fsPath] = {
45 ...stuff,
46 workspaceFolder,
47 };
48 }
49
50 public static getFolder(workspaceFolder: vscode.WorkspaceFolder): IReactNativeProject {
51 return this.projectsCache[workspaceFolder.uri.fsPath];
52 }
53
54 public static delFolder(workspaceFolder: vscode.WorkspaceFolder): void {
55 delete this.projectsCache[workspaceFolder.uri.fsPath];
56 }
57
58 /**
59 * Starts the React Native packager
60 */
61 public static startPackager(): Q.Promise<void> {
62 return this.selectProject()
63 .then((project: IReactNativeProject) => {
64 return this.executeCommandInContext("startPackager", project.workspaceFolder, () =>
65 project.packager.isRunning()
66 .then((running) => {
67 return running ? project.packager.stop() : Q.resolve(void 0);
68 })
69 )
70 .then(() => this.runStartPackagerCommandAndUpdateStatus(project));
71 });
72 }
73
74 /**
75 * Starts the Exponent packager
76 */
77 public static startExponentPackager(): Q.Promise<void> {
78 return this.selectProject()
79 .then((project: IReactNativeProject) => {
80 return this.executeCommandInContext("startExponentPackager", project.workspaceFolder, () =>
81 project.packager.isRunning()
82 .then((running) => {
83 return running ? project.packager.stop() : Q.resolve(void 0);
84 })
85 ).then(() =>
86 project.exponentHelper.configureExponentEnvironment()
87 ).then(() => this.runStartPackagerCommandAndUpdateStatus(project, PackagerRunAs.EXPONENT));
88 });
89 }
90
91 /**
92 * Kills the React Native packager invoked by the extension's packager
93 */
94 public static stopPackager(): Q.Promise<void> {
95 return this.selectProject()
96 .then((project: IReactNativeProject) => {
97 return this.executeCommandInContext("stopPackager", project.workspaceFolder, () => project.packager.stop())
98 .then(() => project.packager.statusIndicator.updatePackagerStatus(PackagerStatus.PACKAGER_STOPPED));
99 });
100 }
101
102 public static stopAllPackagers(): Q.Promise<void> {
103 let keys = Object.keys(this.projectsCache);
104 let promises: Q.Promise<void>[] = [];
105 keys.forEach((key) => {
106 let project = this.projectsCache[key];
107 promises.push(this.executeCommandInContext("stopPackager", project.workspaceFolder, () => project.packager.stop())
108 .then(() => project.packager.statusIndicator.updatePackagerStatus(PackagerStatus.PACKAGER_STOPPED)));
109 });
110
111 return Q.all(promises).then(() => {});
112 }
113
114 /**
115 * Restarts the React Native packager
116 */
117 public static restartPackager(): Q.Promise<void> {
118 return this.selectProject()
119 .then((project: IReactNativeProject) => {
120 return this.executeCommandInContext("restartPackager", project.workspaceFolder, () =>
121 this.runRestartPackagerCommandAndUpdateStatus(project));
122 });
123 }
124
125 /**
126 * Execute command to publish to exponent host.
127 */
128 public static publishToExpHost(): Q.Promise<void> {
129 return this.selectProject()
130 .then((project: IReactNativeProject) => {
131 return this.executeCommandInContext("publishToExpHost", project.workspaceFolder, () => {
132 return this.executePublishToExpHost(project).then((didPublish) => {
133 if (!didPublish) {
134 CommandPaletteHandler.logger.warning("Publishing was unsuccessful. Please make sure you are logged in Exponent and your project is a valid Exponentjs project");
135 }
136 });
137 });
138 });
139 }
140
141 /**
142 * Executes the 'react-native run-android' command
143 */
144 public static runAndroid(target: "device" | "simulator" = "simulator"): Q.Promise<void> {
145 TargetPlatformHelper.checkTargetPlatformSupport("android");
146 return this.selectProject()
147 .then((project: IReactNativeProject) => {
148 return this.executeCommandInContext("runAndroid", project.workspaceFolder, () => this.executeWithPackagerRunning(project, () => {
149 const packagerPort = SettingsHelper.getPackagerPort(project.workspaceFolder.uri.fsPath);
150 const runArgs = SettingsHelper.getRunArgs("android", target, project.workspaceFolder.uri);
151 const envArgs = SettingsHelper.getEnvArgs("android", target, project.workspaceFolder.uri);
152 const envFile = SettingsHelper.getEnvFile("android", target, project.workspaceFolder.uri);
153 const projectRoot = SettingsHelper.getReactNativeProjectRoot(project.workspaceFolder.uri.fsPath);
154 const runOptions: IAndroidRunOptions = {
155 platform: "android",
156 workspaceRoot: project.workspaceFolder.uri.fsPath,
157 projectRoot: projectRoot,
158 packagerPort: packagerPort,
159 runArguments: runArgs,
160 env: envArgs,
161 envFile: envFile,
162 };
163 const platform = new AndroidPlatform(runOptions, {
164 packager: project.packager,
165 });
166 return platform.runApp(/*shouldLaunchInAllDevices*/true)
167 .then(() => {
168 return platform.disableJSDebuggingMode();
169 });
170 }));
171 });
172 }
173
174 /**
175 * Executes the 'react-native run-ios' command
176 */
177 public static runIos(target: "device" | "simulator" = "simulator"): Q.Promise<void> {
178 TargetPlatformHelper.checkTargetPlatformSupport("ios");
179 return this.selectProject()
180 .then((project: IReactNativeProject) => {
181 return this.executeCommandInContext("runIos", project.workspaceFolder, () => this.executeWithPackagerRunning(project, () => {
182 const packagerPort = SettingsHelper.getPackagerPort(project.workspaceFolder.uri.fsPath);
183 const runArgs = SettingsHelper.getRunArgs("ios", target, project.workspaceFolder.uri);
184 const envArgs = SettingsHelper.getEnvArgs("ios", target, project.workspaceFolder.uri);
185 const envFile = SettingsHelper.getEnvFile("ios", target, project.workspaceFolder.uri);
186 const platform = new IOSPlatform({
187 platform: "ios",
188 workspaceRoot: project.workspaceFolder.uri.fsPath,
189 projectRoot: project.workspaceFolder.uri.fsPath,
190 packagerPort: packagerPort,
191 runArguments: runArgs,
192 env: envArgs,
193 envFile: envFile,
194 }, { packager: project.packager });
195
196 // Set the Debugging setting to disabled, because in iOS it's persisted across runs of the app
197 return platform.disableJSDebuggingMode()
198 .catch(() => { }) // If setting the debugging mode fails, we ignore the error and we run the run ios command anyways
199 .then(() => {
200 return platform.runApp();
201 });
202 }));
203 });
204 }
205
206 public static showDevMenu(): Q.Promise<void> {
207 return this.selectProject()
208 .then((project: IReactNativeProject) => {
209 AndroidPlatform.showDevMenu()
210 .catch(() => { }); // Ignore any errors
211 IOSPlatform.showDevMenu(project.workspaceFolder.uri.fsPath)
212 .catch(() => { }); // Ignore any errors
213 return Q.resolve(void 0);
214 });
215 }
216
217 public static reloadApp(): Q.Promise<void> {
218 return this.selectProject()
219 .then((project: IReactNativeProject) => {
220 AndroidPlatform.reloadApp()
221 .catch(() => { }); // Ignore any errors
222 IOSPlatform.reloadApp(project.workspaceFolder.uri.fsPath)
223 .catch(() => { }); // Ignore any errors
224 return Q.resolve(void 0);
225 });
226 }
227
228 public static appCenterLogin(): Q.Promise<void> {
229 return this.selectProject()
230 .then((project: IReactNativeProject) => {
231 return CommandPaletteHandler.getAppCenterCommandPalleteHandler(project).run(AppCenterCommandType.Login);
232 });
233 }
234
235 public static appCenterLogout(): Q.Promise<void> {
236 return this.selectProject()
237 .then((project: IReactNativeProject) => {
238 return CommandPaletteHandler.getAppCenterCommandPalleteHandler(project).run(AppCenterCommandType.Logout);
239 });
240 }
241
242 public static appCenterWhoAmI(): Q.Promise<void> {
243 return this.selectProject()
244 .then((project: IReactNativeProject) => {
245 return CommandPaletteHandler.getAppCenterCommandPalleteHandler(project).run(AppCenterCommandType.Whoami);
246 });
247 }
248
249 public static appCenterSetCurrentApp(): Q.Promise<void> {
250 return this.selectProject()
251 .then((project: IReactNativeProject) => {
252 return CommandPaletteHandler.getAppCenterCommandPalleteHandler(project).run(AppCenterCommandType.SetCurrentApp);
253 });
254 }
255
256 public static appCenterGetCurrentApp(): Q.Promise<void> {
257 return this.selectProject()
258 .then((project: IReactNativeProject) => {
259 return CommandPaletteHandler.getAppCenterCommandPalleteHandler(project).run(AppCenterCommandType.GetCurrentApp);
260 });
261 }
262
263 public static appCenterSetCurrentDeployment(): Q.Promise<void> {
264 return this.selectProject()
265 .then((project: IReactNativeProject) => {
266 return CommandPaletteHandler.getAppCenterCommandPalleteHandler(project).run(AppCenterCommandType.SetCurrentDeployment);
267 });
268 }
269
270 public static appCenterCodePushReleaseReact(): Q.Promise<void> {
271 return this.selectProject()
272 .then((project: IReactNativeProject) => {
273 return CommandPaletteHandler.getAppCenterCommandPalleteHandler(project).run(AppCenterCommandType.CodePushReleaseReact);
274 });
275 }
276
277 public static appCenterShowMenu(): Q.Promise<void> {
278 return this.selectProject()
279 .then((project: IReactNativeProject) => {
280 return CommandPaletteHandler.getAppCenterCommandPalleteHandler(project).run(AppCenterCommandType.ShowMenu);
281 });
282 }
283
284 private static runRestartPackagerCommandAndUpdateStatus(project: IReactNativeProject): Q.Promise<void> {
285 return project.packager.restart(SettingsHelper.getPackagerPort(project.workspaceFolder.uri.fsPath))
286 .then(() => project.packager.statusIndicator.updatePackagerStatus(PackagerStatus.PACKAGER_STARTED));
287 }
288
289 /**
290 * Helper method to run packager and update appropriate configurations
291 */
292 private static runStartPackagerCommandAndUpdateStatus(project: IReactNativeProject, startAs: PackagerRunAs = PackagerRunAs.REACT_NATIVE): Q.Promise<any> {
293 if (startAs === PackagerRunAs.EXPONENT) {
294 return this.loginToExponent(project)
295 .then(() =>
296 project.packager.startAsExponent()
297 ).then(exponentUrl => {
298 project.packager.statusIndicator.updatePackagerStatus(PackagerStatus.EXPONENT_PACKAGER_STARTED);
299 CommandPaletteHandler.logger.info("Application is running on Exponent.");
300 const exponentOutput = `Open your exponent app at ${exponentUrl}`;
301 CommandPaletteHandler.logger.info(exponentOutput);
302 vscode.commands.executeCommand("vscode.previewHtml", vscode.Uri.parse(exponentUrl), 1, "Expo QR code");
303 });
304 }
305 return project.packager.startAsReactNative()
306 .then(() => project.packager.statusIndicator.updatePackagerStatus(PackagerStatus.PACKAGER_STARTED));
307 }
308
309 /**
310 * Executes a lambda function after starting the packager
311 * {lambda} The lambda function to be executed
312 */
313 private static executeWithPackagerRunning(project: IReactNativeProject, lambda: () => Q.Promise<void>): Q.Promise<void> {
314 // Start the packager before executing the React-Native command
315 CommandPaletteHandler.logger.info("Attempting to start the React Native packager");
316 return this.runStartPackagerCommandAndUpdateStatus(project).then(lambda);
317 }
318
319 /**
320 * Ensures that we are in a React Native project and then executes the operation
321 * Otherwise, displays an error message banner
322 * {operation} - a function that performs the expected operation
323 */
324 private static executeCommandInContext(rnCommand: string, workspaceFolder: vscode.WorkspaceFolder, operation: () => Q.Promise<void>): Q.Promise<void> {
325 return TelemetryHelper.generate("RNCommand", (generator) => {
326 generator.add("command", rnCommand, false);
327 const projectRoot = SettingsHelper.getReactNativeProjectRoot(workspaceFolder.uri.fsPath);
328 return ReactNativeProjectHelper.isReactNativeProject(projectRoot).then(isRNProject => {
329 generator.add("isRNProject", isRNProject, false);
330 if (isRNProject) {
331 // Bring the log channel to focus
332 CommandPaletteHandler.logger.setFocusOnLogChannel();
333
334 // Execute the operation
335 return operation();
336 } else {
337 vscode.window.showErrorMessage("Current workspace is not a React Native project.");
338 return;
339 }
340 });
341 });
342 }
343
344 /**
345 * 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.
346 */
347 private static executePublishToExpHost(project: IReactNativeProject): Q.Promise<boolean> {
348 CommandPaletteHandler.logger.info("Publishing app to Exponent server. This might take a moment.");
349 return this.loginToExponent(project)
350 .then(user => {
351 CommandPaletteHandler.logger.debug(`Publishing as ${user.username}...`);
352 return this.startExponentPackager()
353 .then(() =>
354 XDL.publish(project.workspaceFolder.uri.fsPath))
355 .then(response => {
356 if (response.err || !response.url) {
357 return false;
358 }
359 const publishedOutput = `App successfully published to ${response.url}`;
360 CommandPaletteHandler.logger.info(publishedOutput);
361 vscode.window.showInformationMessage(publishedOutput);
362 return true;
363 });
364 }).catch(() => {
365 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.");
366 return false;
367 });
368 }
369
370 private static loginToExponent(project: IReactNativeProject): Q.Promise<XDL.IUser> {
371 return project.exponentHelper.loginToExponent(
372 (message, password) => {
373 return Q.Promise((resolve, reject) => {
374 vscode.window.showInputBox({ placeHolder: message, password: password })
375 .then(login => {
376 resolve(login || "");
377 }, reject);
378 });
379 },
380 (message) => {
381 return Q.Promise((resolve, reject) => {
382 vscode.window.showInformationMessage(message)
383 .then(password => {
384 resolve(password || "");
385 }, reject);
386 });
387 }
388 );
389 }
390
391 private static getAppCenterCommandPalleteHandler(project: IReactNativeProject): AppCenterCommandPalleteHandler {
392 if (!CommandPaletteHandler.appCenterCommandPalleteHandler) {
393 CommandPaletteHandler.appCenterCommandPalleteHandler = new AppCenterCommandPalleteHandler(CommandPaletteHandler.logger);
394 }
395 CommandPaletteHandler.appCenterCommandPalleteHandler.AppCenterManager = project.appCenterManager;
396 return CommandPaletteHandler.appCenterCommandPalleteHandler;
397 }
398
399 private static selectProject(): Q.Promise<IReactNativeProject> {
400 let keys = Object.keys(this.projectsCache);
401 if (keys.length > 1) {
402 return Q.Promise((resolve, reject) => {
403 vscode.window.showQuickPick(keys)
404 .then((selected) => {
405 if (selected) {
406 resolve(this.projectsCache[selected]);
407 }
408 }, reject);
409 });
410 } else if (keys.length === 1) {
411 return Q.resolve(this.projectsCache[keys[0]]);
412 } else {
413 return Q.reject();
414 }
415 }
416}
417