microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
ce5e88eec6f7cd03403994764507be96c6af4624

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/appLauncher.ts

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