microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.1.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/exponent/exponentHelper.ts

375lines · 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";
7059d307Patricio Beltran9 years ago7import * as XDL from "./xdlInterface";
66412fdfRuslan Bikkinin7 years ago8import { Package, IPackageInformation } from "../../common/node/package";
e3706a1cRedMickey6 years ago9import { ProjectVersionHelper } from "../../common/projectVersionHelper";
0a68f8dbArtem Egorov8 years ago10import {OutputChannelLogger} from "../log/OutputChannelLogger";
94cd5149Artem Egorov8 years ago11import stripJSONComments = require("strip-json-comments");
d7d405aeYuri Skorokhodov7 years ago12import * as nls from "vscode-nls";
13import { ErrorHelper } from "../../common/error/errorHelper";
14import { InternalErrorCode } from "../../common/error/internalErrorCode";
ce5e88eeYuri Skorokhodov5 years ago15import { FileSystem } from "../../common/node/fileSystem";
2d8af448Yuri Skorokhodov6 years ago16nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
d7d405aeYuri Skorokhodov7 years ago17const 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
ce5e88eeYuri Skorokhodov5 years ago47public configureExponentEnvironment(): Promise<void> {
d1d77244Jimmy Thomson9 years ago48this.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(
ce5e88eeYuri Skorokhodov5 years ago77promptForInformation: (message: string, password: boolean) => Promise<string>,
78showMessage: (message: string) => Promise<string>
79): 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 => {
ce5e88eeYuri Skorokhodov5 years ago98return Promise.reject<XDL.IUser>(error);
d1d77244Jimmy Thomson9 years ago99});
100}
101
ce5e88eeYuri Skorokhodov5 years ago102public getExpPackagerOptions(): Promise<ExpConfigPackager> {
6458f408Nikita Matrosov9 years ago103this.lazilyInitialize();
94cd5149Artem Egorov8 years ago104return this.getFromExpConfig("packagerOpts")
105.then(opts => opts || {});
6458f408Nikita Matrosov9 years ago106}
107
ce5e88eeYuri Skorokhodov5 years ago108public appHasExpoInstalled(): Promise<boolean> {
66412fdfRuslan Bikkinin7 years ago109return 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
ce5e88eeYuri Skorokhodov5 years ago122public appHasExpoRNSDKInstalled(): Promise<boolean> {
66412fdfRuslan Bikkinin7 years ago123return 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
ce5e88eeYuri Skorokhodov5 years ago136public isExpoApp(showProgress: boolean = false): Promise<boolean> {
db6fd42aRuslan Bikkinin7 years ago137if (showProgress) {
138this.logger.logStream("...");
139}
140
ce5e88eeYuri Skorokhodov5 years ago141return Promise.all([
66412fdfRuslan Bikkinin7 years ago142this.appHasExpoInstalled(),
143this.appHasExpoRNSDKInstalled(),
ce5e88eeYuri Skorokhodov5 years ago144]).then(([expoInstalled, expoRNSDKInstalled]) => {
145if (showProgress) this.logger.logStream(".");
146return expoInstalled && expoRNSDKInstalled;
66412fdfRuslan Bikkinin7 years ago147}).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
ce5e88eeYuri Skorokhodov5 years ago168private createExpoEntry(name: string): 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
ce5e88eeYuri Skorokhodov5 years ago178private detectEntry(): Promise<string> {
94cd5149Artem Egorov8 years ago179this.lazilyInitialize();
ce5e88eeYuri Skorokhodov5 years ago180return Promise.all([
94cd5149Artem Egorov8 years ago181this.fs.exists(this.pathToFileInWorkspace(DEFAULT_EXPONENT_INDEX)),
182this.fs.exists(this.pathToFileInWorkspace(DEFAULT_IOS_INDEX)),
183this.fs.exists(this.pathToFileInWorkspace(DEFAULT_ANDROID_INDEX)),
ce5e88eeYuri Skorokhodov5 years ago184]).then(([expo, ios]) => {
185return expo ? this.pathToFileInWorkspace(DEFAULT_EXPONENT_INDEX) :
186ios ? this.pathToFileInWorkspace(DEFAULT_IOS_INDEX) :
187this.pathToFileInWorkspace(DEFAULT_ANDROID_INDEX);
188});
94cd5149Artem Egorov8 years ago189}
1c32fe84Patricio Beltran9 years ago190
94cd5149Artem Egorov8 years ago191private generateFileContent(name: string, entryPoint: string): string {
192return `// This file is automatically generated by VS Code
193// Please do not modify it manually. All changes will be lost.
194var React = require('${this.pathToFileInWorkspace("/node_modules/react")}');
195var { Component } = React;
196var ReactNative = require('${this.pathToFileInWorkspace("/node_modules/react-native")}');
197var { AppRegistry } = ReactNative;
198var entryPoint = require('${entryPoint}');
f6b41bbfAlexander Sorokin9 years ago199AppRegistry.registerRunnable('main', function(appParameters) {
94cd5149Artem Egorov8 years ago200AppRegistry.runApplication('${name}', appParameters);
1ae29ddcJimmy Thomson9 years ago201});`;
1c32fe84Patricio Beltran9 years ago202}
203
ce5e88eeYuri Skorokhodov5 years ago204private patchAppJson(isExpo: boolean = true): Promise<void> {
94cd5149Artem Egorov8 years ago205return this.readAppJson()
206.catch(() => {
207// if app.json doesn't exist but it's ok, we will create it
208return {};
209})
210.then((config: AppJson) => {
211let expoConfig = <ExpConfig>(config.expo || {});
212if (!expoConfig.name || !expoConfig.slug) {
b0af599cJimmy Thomson9 years ago213return this.getPackageName()
94cd5149Artem Egorov8 years ago214.then((name: string) => {
215expoConfig.slug = expoConfig.slug || config.name || name.replace(" ", "-");
216expoConfig.name = expoConfig.name || config.name || name;
217config.expo = expoConfig;
218return config;
b0af599cJimmy Thomson9 years ago219});
220}
221
94cd5149Artem Egorov8 years ago222return config;
223})
66412fdfRuslan Bikkinin7 years ago224.then((config: AppJson) => {
225if (!config.name) {
226return this.getPackageName()
227.then((name: string) => {
228config.name = name;
229return config;
230});
231}
232
233return config;
234})
94cd5149Artem Egorov8 years ago235.then((config: AppJson) => {
236if (!config.expo.sdkVersion) {
237return this.exponentSdk(true)
238.then(sdkVersion => {
239config.expo.sdkVersion = sdkVersion;
240return config;
241});
1c32fe84Patricio Beltran9 years ago242}
66412fdfRuslan Bikkinin7 years ago243
94cd5149Artem Egorov8 years ago244return config;
1c32fe84Patricio Beltran9 years ago245})
94cd5149Artem Egorov8 years ago246.then((config: AppJson) => {
247if (!isExpo) {
66412fdfRuslan Bikkinin7 years ago248// entryPoint must be relative
249// https://docs.expo.io/versions/latest/workflow/configuration/#entrypoint
250config.expo.entryPoint = this.dotvscodePath(EXPONENT_INDEX, false);
94cd5149Artem Egorov8 years ago251}
1c32fe84Patricio Beltran9 years ago252
94cd5149Artem Egorov8 years ago253return config;
254})
255.then((config: AppJson) => {
5c8365a6Artem Egorov8 years ago256return config ? this.writeAppJson(config) : config;
1c32fe84Patricio Beltran9 years ago257})
94cd5149Artem Egorov8 years ago258.then((config: AppJson) => {
ce5e88eeYuri Skorokhodov5 years ago259return isExpo ? Promise.resolve() : this.createExpoEntry(config.expo.name);
1c32fe84Patricio Beltran9 years ago260});
27710197Vladimir Kotikov8 years ago261}
1c32fe84Patricio Beltran9 years ago262
263/**
264* Exponent sdk version that maps to the current react-native version
265* If react native version is not supported it returns null.
266*/
ce5e88eeYuri Skorokhodov5 years ago267private exponentSdk(showProgress: boolean = false): Promise<string> {
94cd5149Artem Egorov8 years ago268if (showProgress) {
0a68f8dbArtem Egorov8 years ago269this.logger.logStream("...");
1c32fe84Patricio Beltran9 years ago270}
94cd5149Artem Egorov8 years ago271
e3706a1cRedMickey6 years ago272return ProjectVersionHelper.getReactNativeVersions(this.projectRootPath)
7fa90b3bRedMickey6 years ago273.then(versions => {
0a68f8dbArtem Egorov8 years ago274if (showProgress) this.logger.logStream(".");
7fa90b3bRedMickey6 years ago275return XDL.mapVersion(versions.reactNativeVersion)
94cd5149Artem Egorov8 years ago276.then(sdkVersion => {
277if (!sdkVersion) {
278return XDL.supportedVersions()
279.then((versions) => {
ce5e88eeYuri Skorokhodov5 years ago280return Promise.reject<string>(ErrorHelper.getInternalError(InternalErrorCode.RNVersionNotSupportedByExponent, versions.join(", ")));
94cd5149Artem Egorov8 years ago281});
282}
283return sdkVersion;
1c32fe84Patricio Beltran9 years ago284});
285});
286}
287
94cd5149Artem Egorov8 years ago288
1c32fe84Patricio Beltran9 years ago289/**
94cd5149Artem Egorov8 years ago290* Name specified on user's package.json
1c32fe84Patricio Beltran9 years ago291*/
ce5e88eeYuri Skorokhodov5 years ago292private getPackageName(): Promise<string> {
94cd5149Artem Egorov8 years ago293return new Package(this.projectRootPath, { fileSystem: this.fs }).name();
294}
295
ce5e88eeYuri Skorokhodov5 years ago296private getExpConfig(): Promise<ExpConfig> {
94cd5149Artem Egorov8 years ago297return this.readExpJson()
298.catch(err => {
299if (err.code === "ENOENT") {
300return this.readAppJson()
301.then((config: AppJson) => {
302return config.expo || {};
303});
1c32fe84Patricio Beltran9 years ago304}
94cd5149Artem Egorov8 years ago305
306return err;
1c32fe84Patricio Beltran9 years ago307});
308}
309
ce5e88eeYuri Skorokhodov5 years ago310private getFromExpConfig(key: string): Promise<any> {
94cd5149Artem Egorov8 years ago311return this.getExpConfig()
312.then((config: ExpConfig) => config[key]);
1c32fe84Patricio Beltran9 years ago313}
314
315/**
94cd5149Artem Egorov8 years ago316* Returns the specified setting from exp.json if it exists
1c32fe84Patricio Beltran9 years ago317*/
ce5e88eeYuri Skorokhodov5 years ago318private readExpJson(): Promise<ExpConfig> {
94cd5149Artem Egorov8 years ago319const expJsonPath = this.pathToFileInWorkspace(EXP_JSON);
320return this.fs.readFile(expJsonPath)
321.then(content => {
ce5e88eeYuri Skorokhodov5 years ago322return JSON.parse(stripJSONComments(content.toString()));
1c32fe84Patricio Beltran9 years ago323});
324}
325
ce5e88eeYuri Skorokhodov5 years ago326private readAppJson(): Promise<AppJson> {
94cd5149Artem Egorov8 years ago327const appJsonPath = this.pathToFileInWorkspace(APP_JSON);
328return this.fs.readFile(appJsonPath)
329.then(content => {
ce5e88eeYuri Skorokhodov5 years ago330return JSON.parse(stripJSONComments(content.toString()));
1c32fe84Patricio Beltran9 years ago331});
332}
333
ce5e88eeYuri Skorokhodov5 years ago334private writeAppJson(config: AppJson): Promise<AppJson> {
94cd5149Artem Egorov8 years ago335const appJsonPath = this.pathToFileInWorkspace(APP_JSON);
336return this.fs.writeFile(appJsonPath, JSON.stringify(config, null, 2))
337.then(() => config);
1c32fe84Patricio Beltran9 years ago338}
339
ce5e88eeYuri Skorokhodov5 years ago340private getAppPackageInformation(): Promise<IPackageInformation> {
66412fdfRuslan Bikkinin7 years ago341return new Package(this.projectRootPath, { fileSystem: this.fs }).parsePackageInformation();
342}
343
1c32fe84Patricio Beltran9 years ago344/**
38edb09eAlexander Sorokin9 years ago345* Path to a given file from the workspace root
1c32fe84Patricio Beltran9 years ago346*/
347private pathToFileInWorkspace(filename: string): string {
94cd5149Artem Egorov8 years ago348return path.join(this.projectRootPath, filename).replace(DBL_SLASHES, "/");
1c32fe84Patricio Beltran9 years ago349}
350
a57e740bPatricio Beltran9 years ago351/**
352* Works as a constructor but only initiliazes when it's actually needed.
353*/
b0af599cJimmy Thomson9 years ago354private lazilyInitialize(): void {
a57e740bPatricio Beltran9 years ago355if (!this.hasInitialized) {
356this.hasInitialized = true;
357
358XDL.configReactNativeVersionWargnings();
38edb09eAlexander Sorokin9 years ago359XDL.attachLoggerStream(this.projectRootPath, {
a57e740bPatricio Beltran9 years ago360stream: {
361write: (chunk: any) => {
362if (chunk.level <= 30) {
0a68f8dbArtem Egorov8 years ago363this.logger.logStream(chunk.msg);
a57e740bPatricio Beltran9 years ago364} else if (chunk.level === 40) {
0a68f8dbArtem Egorov8 years ago365this.logger.warning(chunk.msg);
a57e740bPatricio Beltran9 years ago366} else {
0a68f8dbArtem Egorov8 years ago367this.logger.error(chunk.msg);
a57e740bPatricio Beltran9 years ago368}
369},
370},
371type: "raw",
372});
373}
374}
5c8365a6Artem Egorov8 years ago375}