microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
6e491635e08c261310d93f18da08074c210b3da2

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/appLauncher.ts

287lines · 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 {Packager} from "../common/packager";
7import {RNPackageVersions} from "../common/projectVersionHelper";
8import {ExponentHelper} from "./exponent/exponentHelper";
9import {ReactDirManager} from "./reactDirManager";
10import {SettingsHelper} from "./settingsHelper";
11import {PackagerStatusIndicator} from "./packagerStatusIndicator";
12import {CommandExecutor} from "../common/commandExecutor";
13import {isNullOrUndefined} from "../common/utils";
14import {OutputChannelLogger} from "./log/OutputChannelLogger";
15import {MobilePlatformDeps} from "./generalMobilePlatform";
16import {PlatformResolver} from "./platformResolver";
17import {ProjectVersionHelper} from "../common/projectVersionHelper";
18import {TelemetryHelper} from "../common/telemetryHelper";
19import {ErrorHelper} from "../common/error/errorHelper";
20import {InternalErrorCode} from "../common/error/internalErrorCode";
21import {TargetPlatformHelper} from "../common/targetPlatformHelper";
22import {LogCatMonitor} from "./android/logCatMonitor";
23import {ProjectsStorage} from "./projectsStorage";
24import * as nls from "vscode-nls";
25import { MultipleLifetimesAppWorker } from "../debugger/appWorker";
26const localize = nls.loadMessageBundle();
27
28export class AppLauncher {
29 private appWorker: MultipleLifetimesAppWorker | null;
30 private packager: Packager;
31 private exponentHelper: ExponentHelper;
32 private reactDirManager: ReactDirManager;
33 private workspaceFolder: vscode.WorkspaceFolder;
34 private reactNativeVersions?: RNPackageVersions;
35
36 private logger: OutputChannelLogger = OutputChannelLogger.getMainChannel();
37 private logCatMonitor: LogCatMonitor | null = null;
38
39 public static getAppLauncherByProjectRootPath(projectRootPath: string): AppLauncher {
40 const appLauncher = ProjectsStorage.projectsCache[projectRootPath.toLowerCase()];
41 if (!appLauncher) {
42 throw new Error(`Could not find AppLauncher by the project root path ${projectRootPath}`);
43 }
44
45 return appLauncher;
46 }
47
48 constructor(reactDirManager: ReactDirManager, workspaceFolder: vscode.WorkspaceFolder) {
49 const rootPath = workspaceFolder.uri.fsPath;
50 const projectRootPath = SettingsHelper.getReactNativeProjectRoot(rootPath);
51 this.exponentHelper = new ExponentHelper(rootPath, projectRootPath);
52 const packagerStatusIndicator: PackagerStatusIndicator = new PackagerStatusIndicator();
53 this.packager = new Packager(rootPath, projectRootPath, SettingsHelper.getPackagerPort(workspaceFolder.uri.fsPath), packagerStatusIndicator);
54 this.reactDirManager = reactDirManager;
55 this.workspaceFolder = workspaceFolder;
56 }
57
58 public getPackager(): Packager {
59 return this.packager;
60 }
61
62 public getWorkspaceFolderUri(): vscode.Uri {
63 return this.workspaceFolder.uri;
64 }
65
66 public getWorkspaceFolder(): vscode.WorkspaceFolder {
67 return this.workspaceFolder;
68 }
69
70 public getReactNativeVersions(): RNPackageVersions | undefined {
71 return this.reactNativeVersions;
72 }
73
74 public getExponentHelper(): ExponentHelper {
75 return this.exponentHelper;
76 }
77
78 public getReactDirManager(): ReactDirManager {
79 return this.reactDirManager;
80 }
81
82 public setReactNativeVersions(reactNativeVersions: RNPackageVersions): void {
83 this.reactNativeVersions = reactNativeVersions;
84 }
85
86 public setAppWorker(appWorker: MultipleLifetimesAppWorker): void {
87 this.appWorker = appWorker;
88 }
89
90 public getAppWorker(): MultipleLifetimesAppWorker | null {
91 return this.appWorker;
92 }
93
94 public dispose(): void {
95 this.packager.statusIndicator.dispose();
96 this.packager.stop(true);
97 this.stopMonitoringLogCat();
98 }
99
100 public stopMonitoringLogCat(): void {
101 if (this.logCatMonitor) {
102 this.logCatMonitor.dispose();
103 this.logCatMonitor = null;
104 }
105 }
106
107 public openFileAtLocation(filename: string, lineNumber: number): Q.Promise<void> {
108 return Q.Promise((resolve) => {
109 vscode.workspace.openTextDocument(vscode.Uri.file(filename))
110 .then((document: vscode.TextDocument) => {
111 vscode.window.showTextDocument(document)
112 .then((editor: vscode.TextEditor) => {
113 let range = editor.document.lineAt(lineNumber - 1).range;
114 editor.selection = new vscode.Selection(range.start, range.end);
115 editor.revealRange(range, vscode.TextEditorRevealType.InCenter);
116 resolve(void 0);
117 });
118 });
119 });
120 }
121
122 public getPackagerPort(projectFolder: string): number {
123 return SettingsHelper.getPackagerPort(projectFolder);
124 }
125
126 public launch(launchArgs: any): Promise<any> {
127 let mobilePlatformOptions = this.requestSetup(launchArgs);
128
129 // We add the parameter if it's defined (adapter crashes otherwise)
130 if (!isNullOrUndefined(launchArgs.logCatArguments)) {
131 mobilePlatformOptions.logCatArguments = [this.parseLogCatArguments(launchArgs.logCatArguments)];
132 }
133
134 if (!isNullOrUndefined(launchArgs.variant)) {
135 mobilePlatformOptions.variant = launchArgs.variant;
136 }
137
138 if (!isNullOrUndefined(launchArgs.scheme)) {
139 mobilePlatformOptions.scheme = launchArgs.scheme;
140 }
141
142 if (!isNullOrUndefined(launchArgs.productName)) {
143 mobilePlatformOptions.productName = launchArgs.productName;
144 }
145
146 if (!isNullOrUndefined(launchArgs.launchActivity)) {
147 mobilePlatformOptions.debugLaunchActivity = launchArgs.launchActivity;
148 }
149
150 if (launchArgs.type === "reactnativedirect") {
151 mobilePlatformOptions.isDirect = true;
152 }
153
154 mobilePlatformOptions.packagerPort = SettingsHelper.getPackagerPort(launchArgs.cwd || launchArgs.program);
155 const platformDeps: MobilePlatformDeps = {
156 packager: this.packager,
157 };
158 const mobilePlatform = new PlatformResolver()
159 .resolveMobilePlatform(launchArgs.platform, mobilePlatformOptions, platformDeps);
160 return new Promise((resolve, reject) => {
161 let extProps: any = {
162 platform: {
163 value: launchArgs.platform,
164 isPii: false,
165 },
166 };
167
168 if (mobilePlatformOptions.isDirect) {
169 extProps.isDirect = {
170 value: true,
171 isPii: false,
172 };
173 }
174
175 ProjectVersionHelper.getReactNativePackageVersionsFromNodeModules(mobilePlatformOptions.projectRoot, true)
176 .then(versions => {
177 mobilePlatformOptions.reactNativeVersions = versions;
178 extProps = TelemetryHelper.addPropertyToTelemetryProperties(versions.reactNativeVersion, "reactNativeVersion", extProps);
179 if (launchArgs.platform === "windows") {
180 if (ProjectVersionHelper.isVersionError(versions.reactNativeWindowsVersion)) {
181 throw ErrorHelper.getInternalError(InternalErrorCode.ReactNativeWindowsIsNotInstalled);
182 }
183 extProps = TelemetryHelper.addPropertyToTelemetryProperties(versions.reactNativeWindowsVersion, "reactNativeWindowsVersion", extProps);
184 }
185 TelemetryHelper.generate("launch", extProps, (generator) => {
186 generator.step("checkPlatformCompatibility");
187 TargetPlatformHelper.checkTargetPlatformSupport(mobilePlatformOptions.platform);
188 return mobilePlatform.beforeStartPackager()
189 .then(() => {
190 generator.step("startPackager");
191 return mobilePlatform.startPackager();
192 })
193 .then(() => {
194 // We've seen that if we don't prewarm the bundle cache, the app fails on the first attempt to connect to the debugger logic
195 // and the user needs to Reload JS manually. We prewarm it to prevent that issue
196 generator.step("prewarmBundleCache");
197 this.logger.info(localize("PrewarmingBundleCache", "Prewarming bundle cache. This may take a while ..."));
198 return mobilePlatform.prewarmBundleCache();
199 })
200 .then(() => {
201 generator.step("mobilePlatform.runApp").add("target", mobilePlatformOptions.target, false);
202 this.logger.info(localize("BuildingAndRunningApplication", "Building and running application."));
203 return mobilePlatform.runApp();
204 })
205 .then(() => {
206 if (mobilePlatformOptions.isDirect) {
207 generator.step("mobilePlatform.enableDirectDebuggingMode");
208 if (launchArgs.platform === "android") {
209 this.logger.info(localize("PrepareHermesDebugging", "Prepare Hermes debugging (experimental)"));
210 }
211 return mobilePlatform.disableJSDebuggingMode();
212 }
213 generator.step("mobilePlatform.enableJSDebuggingMode");
214 this.logger.info(localize("EnableJSDebugging", "Enable JS Debugging"));
215 return mobilePlatform.enableJSDebuggingMode();
216 })
217 .then(() => {
218 resolve();
219 })
220 .catch(error => {
221 generator.addError(error);
222 this.logger.error(error);
223 reject(error);
224 });
225 });
226 })
227 .catch(error => {
228 if (error && error.errorCode) {
229 if (error.errorCode === InternalErrorCode.ReactNativePackageIsNotInstalled) {
230 TelemetryHelper.sendErrorEvent(
231 "ReactNativePackageIsNotInstalled",
232 ErrorHelper.getInternalError(InternalErrorCode.ReactNativePackageIsNotInstalled)
233 );
234 } else if (error.errorCode === InternalErrorCode.ReactNativeWindowsIsNotInstalled) {
235 TelemetryHelper.sendErrorEvent(
236 "ReactNativeWindowsPackageIsNotInstalled",
237 ErrorHelper.getInternalError(InternalErrorCode.ReactNativeWindowsIsNotInstalled)
238 );
239 }
240 }
241 this.logger.error(error);
242 reject(error);
243 });
244 });
245 }
246
247 private requestSetup(args: any): any {
248 const workspaceFolder: vscode.WorkspaceFolder = <vscode.WorkspaceFolder>vscode.workspace.getWorkspaceFolder(vscode.Uri.file(args.cwd || args.program));
249 const projectRootPath = this.getProjectRoot(args);
250 let mobilePlatformOptions: any = {
251 workspaceRoot: workspaceFolder.uri.fsPath,
252 projectRoot: projectRootPath,
253 platform: args.platform,
254 env: args.env,
255 envFile: args.envFile,
256 target: args.target || "simulator",
257 };
258
259 if (args.platform === "exponent") {
260 mobilePlatformOptions.expoHostType = args.expoHostType || "tunnel";
261 }
262
263 CommandExecutor.ReactNativeCommand = SettingsHelper.getReactNativeGlobalCommandName(workspaceFolder.uri);
264
265 if (!args.runArguments) {
266 let runArgs = SettingsHelper.getRunArgs(args.platform, args.target || "simulator", workspaceFolder.uri);
267 mobilePlatformOptions.runArguments = runArgs;
268 } else {
269 mobilePlatformOptions.runArguments = args.runArguments;
270 }
271
272 return mobilePlatformOptions;
273 }
274
275 private getProjectRoot(args: any): string {
276 return SettingsHelper.getReactNativeProjectRoot(args.cwd || args.program);
277 }
278
279 /**
280 * Parses log cat arguments to a string
281 */
282 private parseLogCatArguments(userProvidedLogCatArguments: any): string {
283 return Array.isArray(userProvidedLogCatArguments)
284 ? userProvidedLogCatArguments.join(" ") // If it's an array, we join the arguments
285 : userProvidedLogCatArguments; // If not, we leave it as-is
286 }
287}