microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
42b1aa5553c67930776e5dccc6981ba25a79e2ec

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/commandPaletteHandler.ts

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