microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
dbff275ea0f4d1cf0d660999f289999a51d06161

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/common/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 "../node/package";
10import { ReactNativeProjectHelper } from "../reactNativeProjectHelper";
11import { FileSystem } from "../node/fileSystem";
12import { Log } from "../log/log";
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
31 public constructor(workspaceRootPath: string, projectRootPath: string) {
32 this.workspaceRootPath = workspaceRootPath;
33 this.projectRootPath = projectRootPath;
34 this.hasInitialized = false;
35 // Constructor is slim by design. This is to add as less computation as possible
36 // to the initialization of the extension. If a public method is added, make sure
37 // to call this.lazilyInitialize() at the begining of the code to be sure all variables
38 // are correctly initialized.
39 }
40
41 public configureExponentEnvironment(): Q.Promise<void> {
42 this.lazilyInitialize();
43 Log.logMessage("Making sure your project uses the correct dependencies for exponent. This may take a while...");
44 return this.isExpoApp(true)
45 .then(isExpo => {
46 Log.logString(".\n");
47
48 return this.patchAppJson(isExpo);
49 });
50 }
51
52 /**
53 * Returns the current user. If there is none, asks user for username and password and logins to exponent servers.
54 */
55 public loginToExponent(promptForInformation: (message: string, password: boolean) => Q.Promise<string>, showMessage: (message: string) => Q.Promise<string>): Q.Promise<XDL.IUser> {
56 this.lazilyInitialize();
57 return XDL.currentUser()
58 .then((user) => {
59 if (!user) {
60 let username = "";
61 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.")
62 .then(() =>
63 promptForInformation("Exponent username", false)
64 ).then((name) => {
65 username = name;
66 return promptForInformation("Exponent password", true);
67 })
68 .then((password) =>
69 XDL.login(username, password));
70 }
71 return user;
72 })
73 .catch(error => {
74 return Q.reject<XDL.IUser>(error);
75 });
76 }
77
78 public getExpPackagerOptions(): Q.Promise<ExpConfigPackager> {
79 this.lazilyInitialize();
80 return this.getFromExpConfig("packagerOpts")
81 .then(opts => opts || {});
82 }
83
84 /**
85 * Path to a given file inside the .vscode directory
86 */
87 private dotvscodePath(filename: string): string {
88 return path.join(this.workspaceRootPath, ".vscode", filename);
89 }
90
91 private createExpoEntry(name: string): Q.Promise<void> {
92 this.lazilyInitialize();
93 return this.detectEntry()
94 .then((entryPoint: string) => {
95 const content = this.generateFileContent(name, entryPoint);
96 return this.fs.writeFile(this.dotvscodePath(EXPONENT_INDEX), content);
97 });
98
99 }
100
101 private detectEntry(): Q.Promise<string> {
102 this.lazilyInitialize();
103 return Q.all([
104 this.fs.exists(this.pathToFileInWorkspace(DEFAULT_EXPONENT_INDEX)),
105 this.fs.exists(this.pathToFileInWorkspace(DEFAULT_IOS_INDEX)),
106 this.fs.exists(this.pathToFileInWorkspace(DEFAULT_ANDROID_INDEX)),
107 ])
108 .spread((expo: boolean, ios: boolean): string => {
109 return expo ? this.pathToFileInWorkspace(DEFAULT_EXPONENT_INDEX) :
110 ios ? this.pathToFileInWorkspace(DEFAULT_IOS_INDEX) :
111 this.pathToFileInWorkspace(DEFAULT_ANDROID_INDEX);
112 });
113 }
114
115 private generateFileContent(name: string, entryPoint: string): string {
116 return `// This file is automatically generated by VS Code
117// Please do not modify it manually. All changes will be lost.
118var React = require('${this.pathToFileInWorkspace("/node_modules/react")}');
119var { Component } = React;
120var ReactNative = require('${this.pathToFileInWorkspace("/node_modules/react-native")}');
121var { AppRegistry } = ReactNative;
122var entryPoint = require('${entryPoint}');
123AppRegistry.registerRunnable('main', function(appParameters) {
124 AppRegistry.runApplication('${name}', appParameters);
125});`;
126 }
127
128 private patchAppJson(isExpo: boolean = true): Q.Promise<void> {
129 return this.readAppJson()
130 .catch(() => {
131 // if app.json doesn't exist but it's ok, we will create it
132 return {};
133 })
134 .then((config: AppJson) => {
135 let expoConfig = <ExpConfig>(config.expo || {});
136 if (!expoConfig.name || !expoConfig.slug) {
137 return this.getPackageName()
138 .then((name: string) => {
139 expoConfig.slug = expoConfig.slug || config.name || name.replace(" ", "-");
140 expoConfig.name = expoConfig.name || config.name || name;
141 config.expo = expoConfig;
142 return config;
143 });
144 }
145
146 return config;
147 })
148 .then((config: AppJson) => {
149 if (!config.expo.sdkVersion) {
150 return this.exponentSdk(true)
151 .then(sdkVersion => {
152 config.expo.sdkVersion = sdkVersion;
153 return config;
154 });
155 }
156 return config;
157 })
158 .then((config: AppJson) => {
159 if (!isExpo) {
160 config.expo.entryPoint = this.dotvscodePath(EXPONENT_INDEX);
161 }
162
163 return config;
164 })
165 .then((config: AppJson) => {
166 if (config) {
167 return this.writeAppJson(config);
168 }
169 })
170 .then((config: AppJson) => {
171 if (!isExpo) {
172 return this.createExpoEntry(config.expo.name);
173 }
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 Log.logString("...");
184 }
185
186 let reactNativeProjectHelper = new ReactNativeProjectHelper(this.projectRootPath);
187 return reactNativeProjectHelper.getReactNativeVersion()
188 .then(version => {
189 if (showProgress) Log.logString(".");
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 Log.logString("Checking if this is Expo app.");
264 if (showProgress) {
265 Log.logString("...");
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) Log.logString(".");
274 return isExp;
275 }).catch(() => {
276 if (showProgress) {
277 Log.logString(".");
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 Log.logString(chunk.msg);
298 } else if (chunk.level === 40) {
299 Log.logWarning(chunk.msg);
300 } else {
301 Log.logError(chunk.msg);
302 }
303 },
304 },
305 type: "raw",
306 });
307 }
308 }
309}