microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
c036b0d343245c82886cd5906e37ff50b8b30d34

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/appLauncher.ts

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