microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.6.15

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/exponent/exponentHelper.ts

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