microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
c0b32993595df760282a903082cf37d59de18ea8

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/commandPaletteHandler.ts

430lines · 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 public static appCenterSwitchMandatoryPropForRelease(): Q.Promise<void> {
285 return this.selectProject()
286 .then((project: IReactNativeProject) => {
287 return CommandPaletteHandler.getAppCenterCommandPalleteHandler(project).run(AppCenterCommandType.SwitchMandatoryPropForRelease);
288 });
289 }
290
291 public static appCenterSetTargetBinaryVersionForRelease(): Q.Promise<void> {
292 return this.selectProject()
293 .then((project: IReactNativeProject) => {
294 return CommandPaletteHandler.getAppCenterCommandPalleteHandler(project).run(AppCenterCommandType.SetTargetBinaryVersionForRelease);
295 });
296 }
297
298 private static runRestartPackagerCommandAndUpdateStatus(project: IReactNativeProject): Q.Promise<void> {
299 return project.packager.restart(SettingsHelper.getPackagerPort(project.workspaceFolder.uri.fsPath))
300 .then(() => project.packager.statusIndicator.updatePackagerStatus(PackagerStatus.PACKAGER_STARTED));
301 }
302
303 /**
304 * Helper method to run packager and update appropriate configurations
305 */
306 private static runStartPackagerCommandAndUpdateStatus(project: IReactNativeProject, startAs: PackagerRunAs = PackagerRunAs.REACT_NATIVE): Q.Promise<any> {
307 if (startAs === PackagerRunAs.EXPONENT) {
308 return this.loginToExponent(project)
309 .then(() =>
310 project.packager.startAsExponent()
311 ).then(exponentUrl => {
312 project.packager.statusIndicator.updatePackagerStatus(PackagerStatus.EXPONENT_PACKAGER_STARTED);
313 CommandPaletteHandler.logger.info("Application is running on Exponent.");
314 const exponentOutput = `Open your exponent app at ${exponentUrl}`;
315 CommandPaletteHandler.logger.info(exponentOutput);
316 vscode.commands.executeCommand("vscode.previewHtml", vscode.Uri.parse(exponentUrl), 1, "Expo QR code");
317 });
318 }
319 return project.packager.startAsReactNative()
320 .then(() => project.packager.statusIndicator.updatePackagerStatus(PackagerStatus.PACKAGER_STARTED));
321 }
322
323 /**
324 * Executes a lambda function after starting the packager
325 * {lambda} The lambda function to be executed
326 */
327 private static executeWithPackagerRunning(project: IReactNativeProject, lambda: () => Q.Promise<void>): Q.Promise<void> {
328 // Start the packager before executing the React-Native command
329 CommandPaletteHandler.logger.info("Attempting to start the React Native packager");
330 return this.runStartPackagerCommandAndUpdateStatus(project).then(lambda);
331 }
332
333 /**
334 * Ensures that we are in a React Native project and then executes the operation
335 * Otherwise, displays an error message banner
336 * {operation} - a function that performs the expected operation
337 */
338 private static executeCommandInContext(rnCommand: string, workspaceFolder: vscode.WorkspaceFolder, operation: () => Q.Promise<void>): Q.Promise<void> {
339 return TelemetryHelper.generate("RNCommand", (generator) => {
340 generator.add("command", rnCommand, false);
341 const projectRoot = SettingsHelper.getReactNativeProjectRoot(workspaceFolder.uri.fsPath);
342 return ReactNativeProjectHelper.isReactNativeProject(projectRoot).then(isRNProject => {
343 generator.add("isRNProject", isRNProject, false);
344 if (isRNProject) {
345 // Bring the log channel to focus
346 CommandPaletteHandler.logger.setFocusOnLogChannel();
347
348 // Execute the operation
349 return operation();
350 } else {
351 vscode.window.showErrorMessage("Current workspace is not a React Native project.");
352 return;
353 }
354 });
355 });
356 }
357
358 /**
359 * 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.
360 */
361 private static executePublishToExpHost(project: IReactNativeProject): Q.Promise<boolean> {
362 CommandPaletteHandler.logger.info("Publishing app to Exponent server. This might take a moment.");
363 return this.loginToExponent(project)
364 .then(user => {
365 CommandPaletteHandler.logger.debug(`Publishing as ${user.username}...`);
366 return this.startExponentPackager()
367 .then(() =>
368 XDL.publish(project.workspaceFolder.uri.fsPath))
369 .then(response => {
370 if (response.err || !response.url) {
371 return false;
372 }
373 const publishedOutput = `App successfully published to ${response.url}`;
374 CommandPaletteHandler.logger.info(publishedOutput);
375 vscode.window.showInformationMessage(publishedOutput);
376 return true;
377 });
378 }).catch(() => {
379 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.");
380 return false;
381 });
382 }
383
384 private static loginToExponent(project: IReactNativeProject): Q.Promise<XDL.IUser> {
385 return project.exponentHelper.loginToExponent(
386 (message, password) => {
387 return Q.Promise((resolve, reject) => {
388 vscode.window.showInputBox({ placeHolder: message, password: password })
389 .then(login => {
390 resolve(login || "");
391 }, reject);
392 });
393 },
394 (message) => {
395 return Q.Promise((resolve, reject) => {
396 vscode.window.showInformationMessage(message)
397 .then(password => {
398 resolve(password || "");
399 }, reject);
400 });
401 }
402 );
403 }
404
405 private static getAppCenterCommandPalleteHandler(project: IReactNativeProject): AppCenterCommandPalleteHandler {
406 if (!CommandPaletteHandler.appCenterCommandPalleteHandler) {
407 CommandPaletteHandler.appCenterCommandPalleteHandler = new AppCenterCommandPalleteHandler(CommandPaletteHandler.logger);
408 }
409 CommandPaletteHandler.appCenterCommandPalleteHandler.AppCenterManager = project.appCenterManager;
410 return CommandPaletteHandler.appCenterCommandPalleteHandler;
411 }
412
413 private static selectProject(): Q.Promise<IReactNativeProject> {
414 let keys = Object.keys(this.projectsCache);
415 if (keys.length > 1) {
416 return Q.Promise((resolve, reject) => {
417 vscode.window.showQuickPick(keys)
418 .then((selected) => {
419 if (selected) {
420 resolve(this.projectsCache[selected]);
421 }
422 }, reject);
423 });
424 } else if (keys.length === 1) {
425 return Q.resolve(this.projectsCache[keys[0]]);
426 } else {
427 return Q.reject();
428 }
429 }
430}