microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.11.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/exponent/exponentHelper.ts

376lines · modeblame

1c32fe84Patricio Beltran9 years ago1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
94cd5149Artem Egorov8 years ago4/// <reference path="exponentHelper.d.ts" />
5
1c32fe84Patricio Beltran9 years ago6import * as path from "path";
7import * as Q from "q";
7059d307Patricio Beltran9 years ago8import * as XDL from "./xdlInterface";
66412fdfRuslan Bikkinin7 years ago9import { Package, IPackageInformation } from "../../common/node/package";
0a68f8dbArtem Egorov8 years ago10import { ReactNativeProjectHelper } from "../../common/reactNativeProjectHelper";
11import { FileSystem } from "../../common/node/fileSystem";
12import {OutputChannelLogger} from "../log/OutputChannelLogger";
94cd5149Artem Egorov8 years ago13import stripJSONComments = require("strip-json-comments");
d7d405aeYuri Skorokhodov7 years ago14import * as nls from "vscode-nls";
15import { ErrorHelper } from "../../common/error/errorHelper";
16import { InternalErrorCode } from "../../common/error/internalErrorCode";
17const localize = nls.loadMessageBundle();
1c32fe84Patricio Beltran9 years ago18
94cd5149Artem Egorov8 years ago19const APP_JSON = "app.json";
20const EXP_JSON = "exp.json";
1c32fe84Patricio Beltran9 years ago21
22const EXPONENT_INDEX = "exponentIndex.js";
94cd5149Artem Egorov8 years ago23const DEFAULT_EXPONENT_INDEX = "index.js";
1c32fe84Patricio Beltran9 years ago24const DEFAULT_IOS_INDEX = "index.ios.js";
25const DEFAULT_ANDROID_INDEX = "index.android.js";
26
94cd5149Artem Egorov8 years ago27const DBL_SLASHES = /\\/g;
1c32fe84Patricio Beltran9 years ago28
29export class ExponentHelper {
38edb09eAlexander Sorokin9 years ago30private workspaceRootPath: string;
94cd5149Artem Egorov8 years ago31private projectRootPath: string;
2956dba4Yuri Skorokhodov7 years ago32private fs: FileSystem;
a57e740bPatricio Beltran9 years ago33private hasInitialized: boolean;
0a68f8dbArtem Egorov8 years ago34private logger: OutputChannelLogger = OutputChannelLogger.getMainChannel();
1c32fe84Patricio Beltran9 years ago35
2956dba4Yuri Skorokhodov7 years ago36public constructor(workspaceRootPath: string, projectRootPath: string, fs: FileSystem = new FileSystem()) {
38edb09eAlexander Sorokin9 years ago37this.workspaceRootPath = workspaceRootPath;
38this.projectRootPath = projectRootPath;
2956dba4Yuri Skorokhodov7 years ago39this.fs = fs;
a57e740bPatricio Beltran9 years ago40this.hasInitialized = false;
41// Constructor is slim by design. This is to add as less computation as possible
42// to the initialization of the extension. If a public method is added, make sure
b0af599cJimmy Thomson9 years ago43// to call this.lazilyInitialize() at the begining of the code to be sure all variables
a57e740bPatricio Beltran9 years ago44// are correctly initialized.
1c32fe84Patricio Beltran9 years ago45}
46
d1d77244Jimmy Thomson9 years ago47public configureExponentEnvironment(): Q.Promise<void> {
48this.lazilyInitialize();
d7d405aeYuri Skorokhodov7 years ago49this.logger.info(localize("MakingSureYourProjectUsesCorrectExponentDependencies", "Making sure your project uses the correct dependencies for Expo. This may take a while..."));
50this.logger.logStream(localize("CheckingIfThisIsExpoApp", "Checking if this is Expo app."));
66412fdfRuslan Bikkinin7 years ago51let isExpo: boolean;
52return this.isExpoApp(true)
53.then(result => {
54isExpo = result;
55if (!isExpo) {
56return this.appHasExpoInstalled().then((expoInstalled) => {
57if (!expoInstalled) {
58// Expo requires expo package to be installed inside RN application in order to be able to run it
59// https://github.com/expo/expo-cli/issues/255#issuecomment-453214632
60this.logger.logStream("\n");
61this.logger.logStream(localize("ExpoPackageIsNotInstalled", "[Warning] Please make sure that expo package is installed locally for your project, otherwise further errors may occur. Please, run \"npm install expo --save-dev\" inside your project to install it."));
62this.logger.logStream("\n");
63}
64});
65}
66return;
67}).then(() => {
0a68f8dbArtem Egorov8 years ago68this.logger.logStream(".\n");
94cd5149Artem Egorov8 years ago69return this.patchAppJson(isExpo);
70});
d1d77244Jimmy Thomson9 years ago71}
72
73/**
74* Returns the current user. If there is none, asks user for username and password and logins to exponent servers.
75*/
5c8365a6Artem Egorov8 years ago76public loginToExponent(
77promptForInformation: (message: string, password: boolean) => Q.Promise<string>,
78showMessage: (message: string) => Q.Promise<string>
79): Q.Promise<XDL.IUser> {
d1d77244Jimmy Thomson9 years ago80this.lazilyInitialize();
81return XDL.currentUser()
82.then((user) => {
83if (!user) {
84let username = "";
d7d405aeYuri Skorokhodov7 years ago85return showMessage(localize("YouNeedToLoginToExpo", "You need to login to Expo. Please provide your Expo account username and password in the input boxes after closing this window. If you don't have an account, please go to https://expo.io to create one."))
d1d77244Jimmy Thomson9 years ago86.then(() =>
d7d405aeYuri Skorokhodov7 years ago87promptForInformation(localize("ExpoUsername", "Expo username"), false)
5c8365a6Artem Egorov8 years ago88).then((name: string) => {
d1d77244Jimmy Thomson9 years ago89username = name;
d7d405aeYuri Skorokhodov7 years ago90return promptForInformation(localize("ExpoPassword", "Expo password"), true);
d1d77244Jimmy Thomson9 years ago91})
5c8365a6Artem Egorov8 years ago92.then((password: string) =>
d1d77244Jimmy Thomson9 years ago93XDL.login(username, password));
94}
95return user;
96})
97.catch(error => {
98return Q.reject<XDL.IUser>(error);
99});
100}
101
94cd5149Artem Egorov8 years ago102public getExpPackagerOptions(): Q.Promise<ExpConfigPackager> {
6458f408Nikita Matrosov9 years ago103this.lazilyInitialize();
94cd5149Artem Egorov8 years ago104return this.getFromExpConfig("packagerOpts")
105.then(opts => opts || {});
6458f408Nikita Matrosov9 years ago106}
107
66412fdfRuslan Bikkinin7 years ago108public appHasExpoInstalled(): Q.Promise<boolean> {
109return this.getAppPackageInformation()
110.then((packageJson: IPackageInformation) => {
111if (packageJson.dependencies && packageJson.dependencies.expo) {
112this.logger.debug("'expo' package is found in 'dependencies' section of package.json");
113return true;
114} else if (packageJson.devDependencies && packageJson.devDependencies.expo) {
115this.logger.debug("'expo' package is found in 'devDependencies' section of package.json");
116return true;
117}
118return false;
119});
120}
121
122public appHasExpoRNSDKInstalled(): Q.Promise<boolean> {
123return this.getAppPackageInformation()
124.then((packageJson: IPackageInformation) => {
125const reactNativeValue: string | undefined = packageJson.dependencies && packageJson.dependencies["react-native"];
126if (reactNativeValue) {
127this.logger.debug(`'react-native' package with value '${reactNativeValue}' is found in 'dependencies' section of package.json`);
128if (reactNativeValue.startsWith("https://github.com/expo/react-native/archive/sdk")) {
129return true;
130}
131}
132return false;
133});
134}
135
136public isExpoApp(showProgress: boolean = false): Q.Promise<boolean> {
db6fd42aRuslan Bikkinin7 years ago137if (showProgress) {
138this.logger.logStream("...");
139}
140
66412fdfRuslan Bikkinin7 years ago141return Q.all([
142this.appHasExpoInstalled(),
143this.appHasExpoRNSDKInstalled(),
144]).spread((expoInstalled, expoRNSDKInstalled) => {
145if (showProgress) this.logger.logStream(".");
146return expoInstalled && expoRNSDKInstalled;
147}).catch((e) => {
148this.logger.error(e.message, e, e.stack);
db6fd42aRuslan Bikkinin7 years ago149if (showProgress) {
150this.logger.logStream(".");
151}
152// Not in a react-native project
153return false;
154});
155}
156
1c32fe84Patricio Beltran9 years ago157/**
94cd5149Artem Egorov8 years ago158* Path to a given file inside the .vscode directory
1c32fe84Patricio Beltran9 years ago159*/
66412fdfRuslan Bikkinin7 years ago160private dotvscodePath(filename: string, isAbsolute: boolean): string {
161let paths = [".vscode", filename];
162if (isAbsolute) {
163paths = [this.workspaceRootPath].concat(...paths);
164}
165return path.join(...paths);
94cd5149Artem Egorov8 years ago166}
167
168private createExpoEntry(name: string): Q.Promise<void> {
b0af599cJimmy Thomson9 years ago169this.lazilyInitialize();
94cd5149Artem Egorov8 years ago170return this.detectEntry()
171.then((entryPoint: string) => {
172const content = this.generateFileContent(name, entryPoint);
66412fdfRuslan Bikkinin7 years ago173return this.fs.writeFile(this.dotvscodePath(EXPONENT_INDEX, true), content);
94cd5149Artem Egorov8 years ago174});
1c32fe84Patricio Beltran9 years ago175
94cd5149Artem Egorov8 years ago176}
1c32fe84Patricio Beltran9 years ago177
94cd5149Artem Egorov8 years ago178private detectEntry(): Q.Promise<string> {
179this.lazilyInitialize();
180return Q.all([
181this.fs.exists(this.pathToFileInWorkspace(DEFAULT_EXPONENT_INDEX)),
182this.fs.exists(this.pathToFileInWorkspace(DEFAULT_IOS_INDEX)),
183this.fs.exists(this.pathToFileInWorkspace(DEFAULT_ANDROID_INDEX)),
184])
2956dba4Yuri Skorokhodov7 years ago185.spread((expo: boolean, ios: boolean): string => {
186return expo ? this.pathToFileInWorkspace(DEFAULT_EXPONENT_INDEX) :
187ios ? this.pathToFileInWorkspace(DEFAULT_IOS_INDEX) :
188this.pathToFileInWorkspace(DEFAULT_ANDROID_INDEX);
189});
94cd5149Artem Egorov8 years ago190}
1c32fe84Patricio Beltran9 years ago191
94cd5149Artem Egorov8 years ago192private generateFileContent(name: string, entryPoint: string): string {
193return `// This file is automatically generated by VS Code
194// Please do not modify it manually. All changes will be lost.
195var React = require('${this.pathToFileInWorkspace("/node_modules/react")}');
196var { Component } = React;
197var ReactNative = require('${this.pathToFileInWorkspace("/node_modules/react-native")}');
198var { AppRegistry } = ReactNative;
199var entryPoint = require('${entryPoint}');
f6b41bbfAlexander Sorokin9 years ago200AppRegistry.registerRunnable('main', function(appParameters) {
94cd5149Artem Egorov8 years ago201AppRegistry.runApplication('${name}', appParameters);
1ae29ddcJimmy Thomson9 years ago202});`;
1c32fe84Patricio Beltran9 years ago203}
204
94cd5149Artem Egorov8 years ago205private patchAppJson(isExpo: boolean = true): Q.Promise<void> {
206return this.readAppJson()
207.catch(() => {
208// if app.json doesn't exist but it's ok, we will create it
209return {};
210})
211.then((config: AppJson) => {
212let expoConfig = <ExpConfig>(config.expo || {});
213if (!expoConfig.name || !expoConfig.slug) {
b0af599cJimmy Thomson9 years ago214return this.getPackageName()
94cd5149Artem Egorov8 years ago215.then((name: string) => {
216expoConfig.slug = expoConfig.slug || config.name || name.replace(" ", "-");
217expoConfig.name = expoConfig.name || config.name || name;
218config.expo = expoConfig;
219return config;
b0af599cJimmy Thomson9 years ago220});
221}
222
94cd5149Artem Egorov8 years ago223return config;
224})
66412fdfRuslan Bikkinin7 years ago225.then((config: AppJson) => {
226if (!config.name) {
227return this.getPackageName()
228.then((name: string) => {
229config.name = name;
230return config;
231});
232}
233
234return config;
235})
94cd5149Artem Egorov8 years ago236.then((config: AppJson) => {
237if (!config.expo.sdkVersion) {
238return this.exponentSdk(true)
239.then(sdkVersion => {
240config.expo.sdkVersion = sdkVersion;
241return config;
242});
1c32fe84Patricio Beltran9 years ago243}
66412fdfRuslan Bikkinin7 years ago244
94cd5149Artem Egorov8 years ago245return config;
1c32fe84Patricio Beltran9 years ago246})
94cd5149Artem Egorov8 years ago247.then((config: AppJson) => {
248if (!isExpo) {
66412fdfRuslan Bikkinin7 years ago249// entryPoint must be relative
250// https://docs.expo.io/versions/latest/workflow/configuration/#entrypoint
251config.expo.entryPoint = this.dotvscodePath(EXPONENT_INDEX, false);
94cd5149Artem Egorov8 years ago252}
1c32fe84Patricio Beltran9 years ago253
94cd5149Artem Egorov8 years ago254return config;
255})
256.then((config: AppJson) => {
5c8365a6Artem Egorov8 years ago257return config ? this.writeAppJson(config) : config;
1c32fe84Patricio Beltran9 years ago258})
94cd5149Artem Egorov8 years ago259.then((config: AppJson) => {
5c8365a6Artem Egorov8 years ago260return isExpo ? Q.resolve(void 0) : this.createExpoEntry(config.expo.name);
1c32fe84Patricio Beltran9 years ago261});
27710197Vladimir Kotikov8 years ago262}
1c32fe84Patricio Beltran9 years ago263
264/**
265* Exponent sdk version that maps to the current react-native version
266* If react native version is not supported it returns null.
267*/
831f4a85Patricio Beltran9 years ago268private exponentSdk(showProgress: boolean = false): Q.Promise<string> {
94cd5149Artem Egorov8 years ago269if (showProgress) {
0a68f8dbArtem Egorov8 years ago270this.logger.logStream("...");
1c32fe84Patricio Beltran9 years ago271}
94cd5149Artem Egorov8 years ago272
2e432a9eArtem Egorov8 years ago273return ReactNativeProjectHelper.getReactNativeVersion(this.projectRootPath)
94cd5149Artem Egorov8 years ago274.then(version => {
0a68f8dbArtem Egorov8 years ago275if (showProgress) this.logger.logStream(".");
94cd5149Artem Egorov8 years ago276return XDL.mapVersion(version)
277.then(sdkVersion => {
278if (!sdkVersion) {
279return XDL.supportedVersions()
280.then((versions) => {
d7d405aeYuri Skorokhodov7 years ago281return Q.reject<string>(ErrorHelper.getInternalError(InternalErrorCode.RNVersionNotSupportedByExponent, versions.join(", ")));
94cd5149Artem Egorov8 years ago282});
283}
284return sdkVersion;
1c32fe84Patricio Beltran9 years ago285});
286});
287}
288
94cd5149Artem Egorov8 years ago289
1c32fe84Patricio Beltran9 years ago290/**
94cd5149Artem Egorov8 years ago291* Name specified on user's package.json
1c32fe84Patricio Beltran9 years ago292*/
94cd5149Artem Egorov8 years ago293private getPackageName(): Q.Promise<string> {
294return new Package(this.projectRootPath, { fileSystem: this.fs }).name();
295}
296
297private getExpConfig(): Q.Promise<ExpConfig> {
298return this.readExpJson()
299.catch(err => {
300if (err.code === "ENOENT") {
301return this.readAppJson()
302.then((config: AppJson) => {
303return config.expo || {};
304});
1c32fe84Patricio Beltran9 years ago305}
94cd5149Artem Egorov8 years ago306
307return err;
1c32fe84Patricio Beltran9 years ago308});
309}
310
94cd5149Artem Egorov8 years ago311private getFromExpConfig(key: string): Q.Promise<any> {
312return this.getExpConfig()
313.then((config: ExpConfig) => config[key]);
1c32fe84Patricio Beltran9 years ago314}
315
316/**
94cd5149Artem Egorov8 years ago317* Returns the specified setting from exp.json if it exists
1c32fe84Patricio Beltran9 years ago318*/
94cd5149Artem Egorov8 years ago319private readExpJson(): Q.Promise<ExpConfig> {
320const expJsonPath = this.pathToFileInWorkspace(EXP_JSON);
321return this.fs.readFile(expJsonPath)
322.then(content => {
323return JSON.parse(stripJSONComments(content));
1c32fe84Patricio Beltran9 years ago324});
325}
326
94cd5149Artem Egorov8 years ago327private readAppJson(): Q.Promise<AppJson> {
328const appJsonPath = this.pathToFileInWorkspace(APP_JSON);
329return this.fs.readFile(appJsonPath)
330.then(content => {
331return JSON.parse(stripJSONComments(content));
1c32fe84Patricio Beltran9 years ago332});
333}
334
94cd5149Artem Egorov8 years ago335private writeAppJson(config: AppJson): Q.Promise<AppJson> {
336const appJsonPath = this.pathToFileInWorkspace(APP_JSON);
337return this.fs.writeFile(appJsonPath, JSON.stringify(config, null, 2))
338.then(() => config);
1c32fe84Patricio Beltran9 years ago339}
340
66412fdfRuslan Bikkinin7 years ago341private getAppPackageInformation(): Q.Promise<IPackageInformation> {
342return new Package(this.projectRootPath, { fileSystem: this.fs }).parsePackageInformation();
343}
344
1c32fe84Patricio Beltran9 years ago345/**
38edb09eAlexander Sorokin9 years ago346* Path to a given file from the workspace root
1c32fe84Patricio Beltran9 years ago347*/
348private pathToFileInWorkspace(filename: string): string {
94cd5149Artem Egorov8 years ago349return path.join(this.projectRootPath, filename).replace(DBL_SLASHES, "/");
1c32fe84Patricio Beltran9 years ago350}
351
a57e740bPatricio Beltran9 years ago352/**
353* Works as a constructor but only initiliazes when it's actually needed.
354*/
b0af599cJimmy Thomson9 years ago355private lazilyInitialize(): void {
a57e740bPatricio Beltran9 years ago356if (!this.hasInitialized) {
357this.hasInitialized = true;
358
359XDL.configReactNativeVersionWargnings();
38edb09eAlexander Sorokin9 years ago360XDL.attachLoggerStream(this.projectRootPath, {
a57e740bPatricio Beltran9 years ago361stream: {
362write: (chunk: any) => {
363if (chunk.level <= 30) {
0a68f8dbArtem Egorov8 years ago364this.logger.logStream(chunk.msg);
a57e740bPatricio Beltran9 years ago365} else if (chunk.level === 40) {
0a68f8dbArtem Egorov8 years ago366this.logger.warning(chunk.msg);
a57e740bPatricio Beltran9 years ago367} else {
0a68f8dbArtem Egorov8 years ago368this.logger.error(chunk.msg);
a57e740bPatricio Beltran9 years ago369}
370},
371},
372type: "raw",
373});
374}
375}
5c8365a6Artem Egorov8 years ago376}