microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.0.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/commandPaletteHandler.ts

420lines · 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 XDL from "./exponent/xdlInterface";
6import {SettingsHelper} from "./settingsHelper";
7import {OutputChannelLogger} from "./log/OutputChannelLogger";
8import {TargetType, GeneralMobilePlatform} from "./generalMobilePlatform";
9import {AndroidPlatform} from "./android/androidPlatform";
10import {IOSPlatform} from "./ios/iOSPlatform";
11import {ProjectVersionHelper} from "../common/projectVersionHelper";
12import {ReactNativeProjectHelper} from "../common/reactNativeProjectHelper";
13import {TargetPlatformHelper} from "../common/targetPlatformHelper";
14import {TelemetryHelper} from "../common/telemetryHelper";
15import {ProjectsStorage} from "./projectsStorage";
16import {IAndroidRunOptions, IIOSRunOptions, PlatformType} from "./launchArgs";
17import {ExponentPlatform} from "./exponent/exponentPlatform";
18import {spawn, ChildProcess} from "child_process";
19import {HostPlatform} from "../common/hostPlatform";
20import {CommandExecutor} from "../common/commandExecutor";
21import * as nls from "vscode-nls";
22import {ErrorHelper} from "../common/error/errorHelper";
23import {InternalErrorCode} from "../common/error/internalErrorCode";
24import {AppLauncher} from "./appLauncher";
25import { AndroidEmulatorManager } from "./android/androidEmulatorManager";
26import { AdbHelper } from "./android/adb";
27nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
28const localize = nls.loadMessageBundle();
29
30export class CommandPaletteHandler {
31 public static elementInspector: ChildProcess | null;
32 private static logger: OutputChannelLogger = OutputChannelLogger.getMainChannel();
33
34 /**
35 * Starts the React Native packager
36 */
37 public static startPackager(): Promise<void> {
38 return this.selectProject()
39 .then((appLauncher: AppLauncher) => {
40 return ProjectVersionHelper.getReactNativePackageVersionsFromNodeModules(appLauncher.getPackager().getProjectPath())
41 .then(versions => {
42 return this.executeCommandInContext("startPackager", appLauncher.getWorkspaceFolder(), () => {
43 return appLauncher.getPackager().isRunning()
44 .then((running) => {
45 return running ? appLauncher.getPackager().stop() : Promise.resolve();
46 });
47 })
48 .then(() => appLauncher.getPackager().start());
49 });
50 });
51 }
52
53 /**
54 * Kills the React Native packager invoked by the extension's packager
55 */
56 public static stopPackager(): Promise<void> {
57 return this.selectProject()
58 .then((appLauncher: AppLauncher) => {
59 return this.executeCommandInContext("stopPackager", appLauncher.getWorkspaceFolder(), () => appLauncher.getPackager().stop());
60 });
61 }
62
63 public static stopAllPackagers(): Promise<void> {
64 let keys = Object.keys(ProjectsStorage.projectsCache);
65 let promises: Promise<void>[] = [];
66 keys.forEach((key) => {
67 let appLauncher = ProjectsStorage.projectsCache[key];
68 promises.push(this.executeCommandInContext("stopPackager", appLauncher.getWorkspaceFolder(), () => appLauncher.getPackager().stop()));
69 });
70
71 return Promise.all(promises).then(() => {});
72 }
73
74 /**
75 * Restarts the React Native packager
76 */
77 public static restartPackager(): Promise<void> {
78 return this.selectProject()
79 .then((appLauncher: AppLauncher) => {
80 return ProjectVersionHelper.getReactNativePackageVersionsFromNodeModules(appLauncher.getPackager().getProjectPath())
81 .then(versions => {
82 return this.executeCommandInContext("restartPackager", appLauncher.getWorkspaceFolder(), () =>
83 this.runRestartPackagerCommandAndUpdateStatus(appLauncher));
84 });
85 });
86 }
87
88 /**
89 * Execute command to publish to exponent host.
90 */
91 public static publishToExpHost(): Promise<void> {
92 return this.selectProject()
93 .then((appLauncher: AppLauncher) => {
94 return this.executeCommandInContext("publishToExpHost", appLauncher.getWorkspaceFolder(), () => {
95 return this.executePublishToExpHost(appLauncher).then((didPublish) => {
96 if (!didPublish) {
97 CommandPaletteHandler.logger.warning(localize("ExponentPublishingWasUnsuccessfulMakeSureYoureLoggedInToExpo", "Publishing was unsuccessful. Please make sure you are logged in Expo and your project is a valid Expo project"));
98 }
99 });
100 });
101 });
102 }
103
104 public static async launchAndroidEmulator(): Promise<void> {
105 const appLauncher = await this.selectProject();
106 const adbHelper = new AdbHelper(appLauncher.getPackager().getProjectPath());
107 const androidEmulatorManager = new AndroidEmulatorManager(adbHelper);
108 const emulator = await androidEmulatorManager.startSelection();
109 if (emulator) {
110 androidEmulatorManager.tryLaunchEmulatorByName(emulator);
111 }
112 }
113
114 /**
115 * Executes the 'react-native run-android' command
116 */
117 public static runAndroid(target: TargetType = "simulator"): Promise<void> {
118 return this.selectProject()
119 .then((appLauncher: AppLauncher) => {
120 TargetPlatformHelper.checkTargetPlatformSupport(PlatformType.Android);
121 return ProjectVersionHelper.getReactNativePackageVersionsFromNodeModules(appLauncher.getPackager().getProjectPath())
122 .then(versions => {
123 appLauncher.setReactNativeVersions(versions);
124 return this.executeCommandInContext("runAndroid", appLauncher.getWorkspaceFolder(), () => {
125 const platform = <AndroidPlatform>this.createPlatform(appLauncher, PlatformType.Android, AndroidPlatform, target);
126 return platform.resolveVirtualDevice(target)
127 .then(() => platform.beforeStartPackager())
128 .then(() => {
129 return platform.startPackager();
130 })
131 .then(() => {
132 return platform.runApp(/*shouldLaunchInAllDevices*/true);
133 })
134 .then(() => {
135 return platform.disableJSDebuggingMode();
136 });
137 });
138 });
139 });
140 }
141
142 /**
143 * Executes the 'react-native run-ios' command
144 */
145 public static runIos(target: TargetType = "simulator"): Promise<void> {
146 return this.selectProject()
147 .then((appLauncher: AppLauncher) => {
148 return ProjectVersionHelper.getReactNativePackageVersionsFromNodeModules(appLauncher.getPackager().getProjectPath())
149 .then(versions => {
150 appLauncher.setReactNativeVersions(versions);
151 TargetPlatformHelper.checkTargetPlatformSupport(PlatformType.iOS);
152 return this.executeCommandInContext("runIos", appLauncher.getWorkspaceFolder(), () => {
153 const platform = <IOSPlatform>this.createPlatform(appLauncher, PlatformType.iOS, IOSPlatform, target);
154 return platform.resolveVirtualDevice(target)
155 .then(() => platform.beforeStartPackager())
156 .then(() => {
157 return platform.startPackager();
158 })
159 .then(() => {
160 // Set the Debugging setting to disabled, because in iOS it's persisted across runs of the app
161 return platform.disableJSDebuggingMode();
162 })
163 .catch(() => { }) // If setting the debugging mode fails, we ignore the error and we run the run ios command anyways
164 .then(() => {
165 return platform.runApp();
166 });
167 });
168 });
169 });
170 }
171
172 /**
173 * Starts the Exponent packager
174 */
175 public static runExponent(): Promise<void> {
176 return this.selectProject()
177 .then((appLauncher: AppLauncher) => {
178 return ProjectVersionHelper.getReactNativePackageVersionsFromNodeModules(appLauncher.getPackager().getProjectPath())
179 .then(versions => {
180 return this.loginToExponent(appLauncher)
181 .then(() => {
182 return this.executeCommandInContext("runExponent", appLauncher.getWorkspaceFolder(), () => {
183 appLauncher.setReactNativeVersions(versions);
184 const platform = <ExponentPlatform>this.createPlatform(appLauncher, PlatformType.Exponent, ExponentPlatform);
185 return platform.beforeStartPackager()
186 .then(() => {
187 return platform.startPackager();
188 })
189 .then(() => {
190 return platform.runApp();
191 });
192 });
193 });
194 });
195 });
196 }
197
198 public static showDevMenu(): Promise<void> {
199 return this.selectProject()
200 .then((appLauncher: AppLauncher) => {
201 const androidPlatform = <AndroidPlatform>this.createPlatform(appLauncher, PlatformType.Android, AndroidPlatform);
202 androidPlatform.showDevMenu()
203 .catch(() => { }); // Ignore any errors
204
205 if (process.platform === "darwin") {
206 const iosPlatform = <IOSPlatform>this.createPlatform(appLauncher, PlatformType.iOS, IOSPlatform);
207 iosPlatform.showDevMenu(appLauncher)
208 .catch(() => { }); // Ignore any errors
209 }
210 return Promise.resolve();
211 });
212 }
213
214 public static reloadApp(): Promise<void> {
215 return this.selectProject()
216 .then((appLauncher: AppLauncher) => {
217 const androidPlatform = <AndroidPlatform>this.createPlatform(appLauncher, PlatformType.Android, AndroidPlatform);
218 androidPlatform.reloadApp()
219 .catch(() => { }); // Ignore any errors
220
221 if (process.platform === "darwin") {
222 const iosPlatform = <IOSPlatform>this.createPlatform(appLauncher, PlatformType.iOS, IOSPlatform);
223 iosPlatform.reloadApp(appLauncher)
224 .catch(() => { }); // Ignore any errors
225 }
226 return Promise.resolve();
227 });
228 }
229
230 public static runElementInspector(): Promise<void> {
231 if (!CommandPaletteHandler.elementInspector) {
232 // Remove the following env variables to prevent running electron app in node mode.
233 // https://github.com/microsoft/vscode/issues/3011#issuecomment-184577502
234 let env = Object.assign({}, process.env);
235 delete env.ATOM_SHELL_INTERNAL_RUN_AS_NODE;
236 delete env.ELECTRON_RUN_AS_NODE;
237 let command = HostPlatform.getNpmCliCommand("react-devtools");
238 CommandPaletteHandler.elementInspector = spawn(command, [], {
239 env,
240 });
241 if (!CommandPaletteHandler.elementInspector.pid) {
242 CommandPaletteHandler.elementInspector = null;
243 return Promise.reject(ErrorHelper.getInternalError(InternalErrorCode.ReactDevtoolsIsNotInstalled));
244 }
245 CommandPaletteHandler.elementInspector.stdout.on("data", (data: string) => {
246 this.logger.info(data);
247 });
248 CommandPaletteHandler.elementInspector.stderr.on("data", (data: string) => {
249 this.logger.error(data);
250 });
251 CommandPaletteHandler.elementInspector.once("exit", () => {
252 CommandPaletteHandler.elementInspector = null;
253 });
254 } else {
255 this.logger.info(localize("AnotherElementInspectorAlreadyRun", "Another element inspector already run"));
256 }
257 return Promise.resolve();
258 }
259
260 public static stopElementInspector(): void {
261 return CommandPaletteHandler.elementInspector ? CommandPaletteHandler.elementInspector.kill() : void 0;
262 }
263
264 public static getPlatformByCommandName(commandName: string): string {
265 commandName = commandName.toLocaleLowerCase();
266
267 if (commandName.indexOf(PlatformType.Android) > -1) {
268 return PlatformType.Android;
269 }
270
271 if (commandName.indexOf(PlatformType.iOS) > -1) {
272 return PlatformType.iOS;
273 }
274
275 if (commandName.indexOf(PlatformType.Exponent) > -1) {
276 return PlatformType.Exponent;
277 }
278
279 return "";
280 }
281
282 private static createPlatform(appLauncher: AppLauncher, platform: PlatformType.iOS | PlatformType.Android | PlatformType.Exponent, platformClass: typeof GeneralMobilePlatform, target?: TargetType): GeneralMobilePlatform {
283 const runOptions = CommandPaletteHandler.getRunOptions(appLauncher, platform, target);
284 return new platformClass(runOptions, {
285 packager: appLauncher.getPackager(),
286 });
287 }
288
289 private static runRestartPackagerCommandAndUpdateStatus(appLauncher: AppLauncher): Promise<void> {
290 return appLauncher.getPackager().restart(SettingsHelper.getPackagerPort(appLauncher.getWorkspaceFolderUri().fsPath));
291 }
292
293 /**
294 * Ensures that we are in a React Native project and then executes the operation
295 * Otherwise, displays an error message banner
296 * {operation} - a function that performs the expected operation
297 */
298 private static executeCommandInContext(rnCommand: string, workspaceFolder: vscode.WorkspaceFolder, operation: () => Promise<void>): Promise<void> {
299 const extProps = {
300 platform: {
301 value: CommandPaletteHandler.getPlatformByCommandName(rnCommand),
302 isPii: false,
303 },
304 };
305
306 return TelemetryHelper.generate("RNCommand", extProps, (generator) => {
307 generator.add("command", rnCommand, false);
308 const projectRoot = SettingsHelper.getReactNativeProjectRoot(workspaceFolder.uri.fsPath);
309 this.logger.debug(`Command palette: run project ${projectRoot} in context`);
310 return ReactNativeProjectHelper.isReactNativeProject(projectRoot)
311 .then(isRNProject => {
312 generator.add("isRNProject", isRNProject, false);
313 if (isRNProject) {
314 // Bring the log channel to focus
315 this.logger.setFocusOnLogChannel();
316
317 // Execute the operation
318 return operation();
319 } else {
320 vscode.window.showErrorMessage(`${projectRoot} workspace is not a React Native project.`);
321 return;
322 }
323 });
324 });
325 }
326
327 /**
328 * 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.
329 */
330 private static executePublishToExpHost(appLauncher: AppLauncher): Promise<boolean> {
331 CommandPaletteHandler.logger.info(localize("PublishingAppToExponentServer", "Publishing app to Expo server. This might take a moment."));
332 return this.loginToExponent(appLauncher)
333 .then(user => {
334 CommandPaletteHandler.logger.debug(`Publishing as ${user.username}...`);
335 return this.runExponent()
336 .then(() =>
337 XDL.publish(appLauncher.getWorkspaceFolderUri().fsPath))
338 .then(response => {
339 if (response.err || !response.url) {
340 return false;
341 }
342 const publishedOutput = localize("ExpoAppSuccessfullyPublishedTo", "Expo app successfully published to {0}", response.url);
343 CommandPaletteHandler.logger.info(publishedOutput);
344 vscode.window.showInformationMessage(publishedOutput);
345 return true;
346 });
347 });
348 }
349
350 private static loginToExponent(appLauncher: AppLauncher): Promise<XDL.IUser> {
351 return appLauncher.getExponentHelper().loginToExponent(
352 (message, password) => {
353 return new Promise((resolve, reject) => {
354 vscode.window.showInputBox({ placeHolder: message, password: password })
355 .then(login => {
356 resolve(login || "");
357 }, reject);
358 });
359 },
360 (message) => {
361 return new Promise((resolve, reject) => {
362 vscode.window.showInformationMessage(message)
363 .then(password => {
364 resolve(password || "");
365 }, reject);
366 });
367 }
368 )
369 .catch((err) => {
370 CommandPaletteHandler.logger.warning(localize("ExpoErrorOccuredMakeSureYouAreLoggedIn", "An error has occured. Please make sure you are logged in to Expo, your project is setup correctly for publishing and your packager is running as Expo."));
371 throw err;
372 });
373 }
374
375 private static selectProject(): Promise<AppLauncher> {
376 let keys = Object.keys(ProjectsStorage.projectsCache);
377 if (keys.length > 1) {
378 return new Promise((resolve, reject) => {
379 vscode.window.showQuickPick(keys)
380 .then((selected) => {
381 if (selected) {
382 this.logger.debug(`Command palette: selected project ${selected}`);
383 resolve(ProjectsStorage.projectsCache[selected]);
384 }
385 }, reject);
386 });
387 } else if (keys.length === 1) {
388 this.logger.debug(`Command palette: once project ${keys[0]}`);
389 return Promise.resolve(ProjectsStorage.projectsCache[keys[0]]);
390 } else {
391 return Promise.reject(ErrorHelper.getInternalError(InternalErrorCode.WorkspaceNotFound, "Current workspace does not contain React Native projects."));
392 }
393 }
394
395 private static getRunOptions(appLauncher: AppLauncher, platform: PlatformType.iOS | PlatformType.Android | PlatformType.Exponent, target: TargetType = "simulator"): IAndroidRunOptions | IIOSRunOptions {
396 const packagerPort = SettingsHelper.getPackagerPort(appLauncher.getWorkspaceFolderUri().fsPath);
397 const runArgs = SettingsHelper.getRunArgs(platform, target, appLauncher.getWorkspaceFolderUri());
398 const envArgs = SettingsHelper.getEnvArgs(platform, target, appLauncher.getWorkspaceFolderUri());
399 const envFile = SettingsHelper.getEnvFile(platform, target, appLauncher.getWorkspaceFolderUri());
400 const projectRoot = SettingsHelper.getReactNativeProjectRoot(appLauncher.getWorkspaceFolderUri().fsPath);
401 const runOptions: IAndroidRunOptions | IIOSRunOptions = {
402 platform: platform,
403 workspaceRoot: appLauncher.getWorkspaceFolderUri().fsPath,
404 projectRoot: projectRoot,
405 packagerPort: packagerPort,
406 runArguments: runArgs,
407 env: envArgs,
408 envFile: envFile,
409 reactNativeVersions: appLauncher.getReactNativeVersions() || {reactNativeVersion: "", reactNativeWindowsVersion: ""},
410 };
411
412 if (platform === PlatformType.iOS && target === "device") {
413 runOptions.target = "device";
414 }
415
416 CommandExecutor.ReactNativeCommand = SettingsHelper.getReactNativeGlobalCommandName(appLauncher.getWorkspaceFolderUri());
417
418 return runOptions;
419 }
420}
421