microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
e23d18413023bf3cf53b60f216b32f9b33263a28

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/appLauncher.ts

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