microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.4.3

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/exponent/exponentHelper.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
4/// <reference path="exponentHelper.d.ts" />
5
6import * as path from "path";
7import * as Q from "q";
8import * as XDL from "./xdlInterface";
9import { Package } from "../../common/node/package";
10import { ReactNativeProjectHelper } from "../../common/reactNativeProjectHelper";
11import { FileSystem } from "../../common/node/fileSystem";
12import {OutputChannelLogger} from "../log/OutputChannelLogger";
13import stripJSONComments = require("strip-json-comments");
14
15const APP_JSON = "app.json";
16const EXP_JSON = "exp.json";
17
18const EXPONENT_INDEX = "exponentIndex.js";
19const DEFAULT_EXPONENT_INDEX = "index.js";
20const DEFAULT_IOS_INDEX = "index.ios.js";
21const DEFAULT_ANDROID_INDEX = "index.android.js";
22
23const DBL_SLASHES = /\\/g;
24
25export class ExponentHelper {
26 private workspaceRootPath: string;
27 private projectRootPath: string;
28 private fs: FileSystem;
29 private hasInitialized: boolean;
30 private logger: OutputChannelLogger = OutputChannelLogger.getMainChannel();
31
32 public constructor(workspaceRootPath: string, projectRootPath: string) {
33 this.workspaceRootPath = workspaceRootPath;
34 this.projectRootPath = projectRootPath;
35 this.hasInitialized = false;
36 // Constructor is slim by design. This is to add as less computation as possible
37 // to the initialization of the extension. If a public method is added, make sure
38 // to call this.lazilyInitialize() at the begining of the code to be sure all variables
39 // are correctly initialized.
40 }
41
42 public configureExponentEnvironment(): Q.Promise<void> {
43 this.lazilyInitialize();
44 this.logger.info("Making sure your project uses the correct dependencies for exponent. This may take a while...");
45 return this.isExpoApp(true)
46 .then(isExpo => {
47 this.logger.logStream(".\n");
48
49 return this.patchAppJson(isExpo);
50 });
51 }
52
53 /**
54 * Returns the current user. If there is none, asks user for username and password and logins to exponent servers.
55 */
56 public loginToExponent(
57 promptForInformation: (message: string, password: boolean) => Q.Promise<string>,
58 showMessage: (message: string) => Q.Promise<string>
59 ): Q.Promise<XDL.IUser> {
60 this.lazilyInitialize();
61 return XDL.currentUser()
62 .then((user) => {
63 if (!user) {
64 let username = "";
65 return showMessage("You need to login to exponent. Please provide username and password to login. If you don't have an account we will create one for you.")
66 .then(() =>
67 promptForInformation("Exponent username", false)
68 ).then((name: string) => {
69 username = name;
70 return promptForInformation("Exponent password", true);
71 })
72 .then((password: string) =>
73 XDL.login(username, password));
74 }
75 return user;
76 })
77 .catch(error => {
78 return Q.reject<XDL.IUser>(error);
79 });
80 }
81
82 public getExpPackagerOptions(): Q.Promise<ExpConfigPackager> {
83 this.lazilyInitialize();
84 return this.getFromExpConfig("packagerOpts")
85 .then(opts => opts || {});
86 }
87
88 /**
89 * Path to a given file inside the .vscode directory
90 */
91 private dotvscodePath(filename: string): string {
92 return path.join(this.workspaceRootPath, ".vscode", filename);
93 }
94
95 private createExpoEntry(name: string): Q.Promise<void> {
96 this.lazilyInitialize();
97 return this.detectEntry()
98 .then((entryPoint: string) => {
99 const content = this.generateFileContent(name, entryPoint);
100 return this.fs.writeFile(this.dotvscodePath(EXPONENT_INDEX), content);
101 });
102
103 }
104
105 private detectEntry(): Q.Promise<string> {
106 this.lazilyInitialize();
107 return Q.all([
108 this.fs.exists(this.pathToFileInWorkspace(DEFAULT_EXPONENT_INDEX)),
109 this.fs.exists(this.pathToFileInWorkspace(DEFAULT_IOS_INDEX)),
110 this.fs.exists(this.pathToFileInWorkspace(DEFAULT_ANDROID_INDEX)),
111 ])
112 .spread((expo: boolean, ios: boolean): string => {
113 return expo ? this.pathToFileInWorkspace(DEFAULT_EXPONENT_INDEX) :
114 ios ? this.pathToFileInWorkspace(DEFAULT_IOS_INDEX) :
115 this.pathToFileInWorkspace(DEFAULT_ANDROID_INDEX);
116 });
117 }
118
119 private generateFileContent(name: string, entryPoint: string): string {
120 return `// This file is automatically generated by VS Code
121// Please do not modify it manually. All changes will be lost.
122var React = require('${this.pathToFileInWorkspace("/node_modules/react")}');
123var { Component } = React;
124var ReactNative = require('${this.pathToFileInWorkspace("/node_modules/react-native")}');
125var { AppRegistry } = ReactNative;
126var entryPoint = require('${entryPoint}');
127AppRegistry.registerRunnable('main', function(appParameters) {
128 AppRegistry.runApplication('${name}', appParameters);
129});`;
130 }
131
132 private patchAppJson(isExpo: boolean = true): Q.Promise<void> {
133 return this.readAppJson()
134 .catch(() => {
135 // if app.json doesn't exist but it's ok, we will create it
136 return {};
137 })
138 .then((config: AppJson) => {
139 let expoConfig = <ExpConfig>(config.expo || {});
140 if (!expoConfig.name || !expoConfig.slug) {
141 return this.getPackageName()
142 .then((name: string) => {
143 expoConfig.slug = expoConfig.slug || config.name || name.replace(" ", "-");
144 expoConfig.name = expoConfig.name || config.name || name;
145 config.expo = expoConfig;
146 return config;
147 });
148 }
149
150 return config;
151 })
152 .then((config: AppJson) => {
153 if (!config.expo.sdkVersion) {
154 return this.exponentSdk(true)
155 .then(sdkVersion => {
156 config.expo.sdkVersion = sdkVersion;
157 return config;
158 });
159 }
160 return config;
161 })
162 .then((config: AppJson) => {
163 if (!isExpo) {
164 config.expo.entryPoint = this.dotvscodePath(EXPONENT_INDEX);
165 }
166
167 return config;
168 })
169 .then((config: AppJson) => {
170 return config ? this.writeAppJson(config) : config;
171 })
172 .then((config: AppJson) => {
173 return isExpo ? Q.resolve(void 0) : this.createExpoEntry(config.expo.name);
174 });
175 }
176
177 /**
178 * Exponent sdk version that maps to the current react-native version
179 * If react native version is not supported it returns null.
180 */
181 private exponentSdk(showProgress: boolean = false): Q.Promise<string> {
182 if (showProgress) {
183 this.logger.logStream("...");
184 }
185
186 let reactNativeProjectHelper = new ReactNativeProjectHelper(this.projectRootPath);
187 return reactNativeProjectHelper.getReactNativeVersion()
188 .then(version => {
189 if (showProgress) this.logger.logStream(".");
190 return XDL.mapVersion(version)
191 .then(sdkVersion => {
192 if (!sdkVersion) {
193 return XDL.supportedVersions()
194 .then((versions) => {
195 return Q.reject<string>(new Error(`React Native version not supported by exponent. Major versions supported: ${versions.join(", ")}`));
196 });
197 }
198 return sdkVersion;
199 });
200 });
201 }
202
203
204 /**
205 * Name specified on user's package.json
206 */
207 private getPackageName(): Q.Promise<string> {
208 return new Package(this.projectRootPath, { fileSystem: this.fs }).name();
209 }
210
211 private getExpConfig(): Q.Promise<ExpConfig> {
212 return this.readExpJson()
213 .catch(err => {
214 if (err.code === "ENOENT") {
215 return this.readAppJson()
216 .then((config: AppJson) => {
217 return config.expo || {};
218 });
219 }
220
221 return err;
222 });
223 }
224
225 private getFromExpConfig(key: string): Q.Promise<any> {
226 return this.getExpConfig()
227 .then((config: ExpConfig) => config[key]);
228 }
229
230 /**
231 * Returns the specified setting from exp.json if it exists
232 */
233 private readExpJson(): Q.Promise<ExpConfig> {
234 const expJsonPath = this.pathToFileInWorkspace(EXP_JSON);
235 return this.fs.readFile(expJsonPath)
236 .then(content => {
237 return JSON.parse(stripJSONComments(content));
238 });
239 }
240
241 private readAppJson(): Q.Promise<AppJson> {
242 const appJsonPath = this.pathToFileInWorkspace(APP_JSON);
243 return this.fs.readFile(appJsonPath)
244 .then(content => {
245 return JSON.parse(stripJSONComments(content));
246 });
247 }
248
249 private writeAppJson(config: AppJson): Q.Promise<AppJson> {
250 const appJsonPath = this.pathToFileInWorkspace(APP_JSON);
251 return this.fs.writeFile(appJsonPath, JSON.stringify(config, null, 2))
252 .then(() => config);
253 }
254
255 /**
256 * Path to a given file from the workspace root
257 */
258 private pathToFileInWorkspace(filename: string): string {
259 return path.join(this.projectRootPath, filename).replace(DBL_SLASHES, "/");
260 }
261
262 private isExpoApp(showProgress: boolean = false): Q.Promise<boolean> {
263 this.logger.logStream("Checking if this is Expo app.");
264 if (showProgress) {
265 this.logger.logStream("...");
266 }
267
268 const packageJsonPath = this.pathToFileInWorkspace("package.json");
269 return this.fs.readFile(packageJsonPath)
270 .then(content => {
271 const packageJson = JSON.parse(content);
272 const isExp = packageJson.dependencies && !!packageJson.dependencies.expo || false;
273 if (showProgress) this.logger.logStream(".");
274 return isExp;
275 }).catch(() => {
276 if (showProgress) {
277 this.logger.logStream(".");
278 }
279 // Not in a react-native project
280 return false;
281 });
282 }
283
284 /**
285 * Works as a constructor but only initiliazes when it's actually needed.
286 */
287 private lazilyInitialize(): void {
288 if (!this.hasInitialized) {
289 this.hasInitialized = true;
290 this.fs = new FileSystem();
291
292 XDL.configReactNativeVersionWargnings();
293 XDL.attachLoggerStream(this.projectRootPath, {
294 stream: {
295 write: (chunk: any) => {
296 if (chunk.level <= 30) {
297 this.logger.logStream(chunk.msg);
298 } else if (chunk.level === 40) {
299 this.logger.warning(chunk.msg);
300 } else {
301 this.logger.error(chunk.msg);
302 }
303 },
304 },
305 type: "raw",
306 });
307 }
308 }
309}
310