microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
ff70c05a6c821e99785e3f91dca1daf27094e4fd

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/appLauncher.ts

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