microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
3c2eeb5c9c12252cabd328afa31e79f80b5dd394

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/common/packager.ts

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