microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
98bdec43841fff67dedc8e3e96ca0ffcac85935a

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/common/packager.ts

172lines · 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 {ChildProcess} from "child_process";
5import {CommandExecutor} from "./commandExecutor";
6import {Log, LogLevel} from "./log";
7import {Node} from "./node/node";
8import {OutputChannel} from "vscode";
9import {Package} from "./node/package";
10import {PromiseUtil} from "./node/promise";
11import {Request} from "./node/request";
12
13import * as Q from "q";
14import * as path from "path";
15
16export class Packager {
17 // TODO: Make the port configurable via a launch argument
18 public static PORT = "8081";
19 public static HOST = `localhost:${Packager.PORT}`;
20 public static DEBUGGER_WORKER_FILE_BASENAME = "debuggerWorker";
21 public static DEBUGGER_WORKER_FILENAME = Packager.DEBUGGER_WORKER_FILE_BASENAME + ".js";
22
23 private projectPath: string;
24 private packagerProcess: ChildProcess;
25 private sourcesStoragePath: string;
26
27 private static JS_INJECTOR_FILENAME = "opn-main.js";
28 private static JS_INJECTOR_FILEPATH = path.resolve(path.dirname(path.dirname(__dirname)), "js-patched", Packager.JS_INJECTOR_FILENAME);
29 private static NODE_MODULES_FODLER_NAME = "node_modules";
30 private static OPN_PACKAGE_NAME = "opn";
31 private static REACT_NATIVE_PACKAGE_NAME = "react-native";
32 private static OPN_PACKAGE_MAIN_FILENAME = "index.js";
33
34 constructor(projectPath: string, sourcesStoragePath?: string) {
35 this.projectPath = projectPath;
36 this.sourcesStoragePath = sourcesStoragePath;
37 }
38
39
40 public start(outputChannel?: OutputChannel): Q.Promise<void> {
41 let executedStartPackagerCmd = false;
42 return this.isRunning()
43 .then(running => {
44 if (!running) {
45 return this.monkeyPatchOpnForRNPackager()
46 .then(() => {
47 let args = ["--port", Packager.PORT];
48 let childEnvForDebugging = Object.assign({}, process.env, { REACT_DEBUGGER: "echo A debugger is not needed: " });
49
50 Log.logMessage("Starting Packager", outputChannel);
51 // The packager will continue running while we debug the application, so we can"t
52 // wait for this command to finish
53
54 let spawnOptions = { env: childEnvForDebugging };
55
56 // TODO - PROMISE: We need to consume the result of this spawn
57 new CommandExecutor(this.projectPath).spawnReactPackager(args, spawnOptions, outputChannel).then((packagerProcess) => {
58 this.packagerProcess = packagerProcess;
59 executedStartPackagerCmd = true;
60 });
61 });
62 }
63 })
64 .then(() =>
65 this.awaitStart())
66 .then(() => {
67 if (executedStartPackagerCmd) {
68 Log.logMessage("Packager started.", outputChannel);
69 } else {
70 Log.logMessage("Packager is already running.", outputChannel);
71 if (!outputChannel) {
72 // TODO: This warning is printted incorrectly when the packager was started from the command palette. Fix it.
73 Log.logWarning("Debugging is not supported if the React Native Packager is not started within VS Code. If debugging fails, please kill other active React Native packager processes and retry.", outputChannel);
74 }
75 }
76
77 if (this.sourcesStoragePath) {
78 return this.downloadDebuggerWorker().then(() => {
79 Log.logMessage("Downloaded debuggerWorker.js (Logic to run the React Native app) from the Packager.");
80 });
81 }
82 });
83 }
84
85 public stop(outputChannel?: OutputChannel): Q.Promise<void> {
86 return new CommandExecutor(this.projectPath).killReactPackager(this.packagerProcess, outputChannel).then(() =>
87 this.packagerProcess = null);
88 }
89
90 public prewarmBundleCache(platform: string) {
91 let bundleURL = `http://${Packager.HOST}/index.${platform}.bundle`;
92 Log.logInternalMessage(LogLevel.Info, "About to get: " + bundleURL);
93 return new Request().request(bundleURL, true).then(() => {
94 Log.logMessage("The Bundle Cache was prewarmed.");
95 }).catch(() => {
96 // The attempt to prefetch the bundle failed.
97 // This may be because the bundle is not index.* so we shouldn't treat this as fatal.
98 });
99 }
100
101 private isRunning(): Q.Promise<boolean> {
102 let statusURL = `http://${Packager.HOST}/status`;
103
104 return new Request().request(statusURL)
105 .then((body: string) => {
106 return body === "packager-status:running";
107 },
108 (error: any) => {
109 return false;
110 });
111 }
112
113 private awaitStart(retryCount = 30, delay = 2000): Q.Promise<boolean> {
114 let pu: PromiseUtil = new PromiseUtil();
115 return pu.retryAsync(() => this.isRunning(), (running) => running, retryCount, delay, "Could not start the packager.");
116 }
117
118 private downloadDebuggerWorker(): Q.Promise<void> {
119 let debuggerWorkerURL = `http://${Packager.HOST}/${Packager.DEBUGGER_WORKER_FILENAME}`;
120 let debuggerWorkerLocalPath = path.join(this.sourcesStoragePath, Packager.DEBUGGER_WORKER_FILENAME);
121 Log.logInternalMessage(LogLevel.Info, "About to download: " + debuggerWorkerURL + " to: " + debuggerWorkerLocalPath);
122 return new Request().request(debuggerWorkerURL, true).then((body: string) => {
123 return new Node.FileSystem().writeFile(debuggerWorkerLocalPath, body);
124 });
125 }
126
127 private findOpnPackage(): Q.Promise<string> {
128 try {
129 let flatDependencyPackagePath = path.resolve(this.projectPath, Packager.NODE_MODULES_FODLER_NAME,
130 Packager.OPN_PACKAGE_NAME, Packager.OPN_PACKAGE_MAIN_FILENAME);
131
132 let nestedDependencyPackagePath = path.resolve(this.projectPath, Packager.NODE_MODULES_FODLER_NAME,
133 Packager.REACT_NATIVE_PACKAGE_NAME, Packager.NODE_MODULES_FODLER_NAME, Packager.OPN_PACKAGE_NAME, Packager.OPN_PACKAGE_MAIN_FILENAME);
134
135 let fsHelper = new Node.FileSystem();
136
137 // Attempt to find the 'opn' package directly under the project's node_modules folder (node4 +)
138 // Else, attempt to find the package within the dependent node_modules of react-native package
139 let possiblePaths = [flatDependencyPackagePath, nestedDependencyPackagePath];
140 return Q.any(possiblePaths.map(path =>
141 fsHelper.exists(path).then(exists =>
142 exists
143 ? Q.resolve(path)
144 : Q.reject<string>("opn package location not found"))));
145 } catch (err) {
146 console.error("The package \'opn\' was not found." + err);
147 }
148 }
149
150 private monkeyPatchOpnForRNPackager(): Q.Promise<void> {
151 let opnPackage: Package;
152 let destnFilePath: string;
153
154 // Finds the 'opn' package
155 return this.findOpnPackage()
156 .then((opnIndexFilePath) => {
157 destnFilePath = opnIndexFilePath;
158 // Read the package's "package.json"
159 opnPackage = new Package(path.resolve(path.dirname(destnFilePath)));
160 return opnPackage.parsePackageInformation();
161 }).then((packageJson) => {
162 if (packageJson.main !== Packager.JS_INJECTOR_FILENAME) {
163 // Copy over the patched 'opn' main file
164 return new Node.FileSystem().copyFile(Packager.JS_INJECTOR_FILEPATH, path.resolve(path.dirname(destnFilePath), Packager.JS_INJECTOR_FILENAME))
165 .then(() => {
166 // Write/over-write the "main" attribute with the new file
167 return opnPackage.setMainFile(Packager.JS_INJECTOR_FILENAME);
168 });
169 }
170 });
171 }
172}
173