microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
a289475be0da2ee07b9b056760f2f0a3076877b2

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/common/packager.ts

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