microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.3.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/exponent/exponentHelper.ts

398lines · 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";
34472878RedMickey5 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";
34472878RedMickey5 years ago16nls.config({
17messageFormat: nls.MessageFormat.bundle,
18bundleFormat: nls.BundleFormat.standalone,
19})();
d7d405aeYuri Skorokhodov7 years ago20const localize = nls.loadMessageBundle();
1c32fe84Patricio Beltran9 years ago21
94cd5149Artem Egorov8 years ago22const APP_JSON = "app.json";
23const EXP_JSON = "exp.json";
1c32fe84Patricio Beltran9 years ago24
25const EXPONENT_INDEX = "exponentIndex.js";
94cd5149Artem Egorov8 years ago26const DEFAULT_EXPONENT_INDEX = "index.js";
1c32fe84Patricio Beltran9 years ago27const DEFAULT_IOS_INDEX = "index.ios.js";
28const DEFAULT_ANDROID_INDEX = "index.android.js";
29
94cd5149Artem Egorov8 years ago30const DBL_SLASHES = /\\/g;
1c32fe84Patricio Beltran9 years ago31
32export class ExponentHelper {
38edb09eAlexander Sorokin9 years ago33private workspaceRootPath: string;
94cd5149Artem Egorov8 years ago34private projectRootPath: string;
2956dba4Yuri Skorokhodov7 years ago35private fs: FileSystem;
a57e740bPatricio Beltran9 years ago36private hasInitialized: boolean;
0a68f8dbArtem Egorov8 years ago37private logger: OutputChannelLogger = OutputChannelLogger.getMainChannel();
1c32fe84Patricio Beltran9 years ago38
34472878RedMickey5 years ago39public constructor(
40workspaceRootPath: string,
41projectRootPath: string,
42fs: FileSystem = new FileSystem(),
43) {
38edb09eAlexander Sorokin9 years ago44this.workspaceRootPath = workspaceRootPath;
45this.projectRootPath = projectRootPath;
2956dba4Yuri Skorokhodov7 years ago46this.fs = fs;
a57e740bPatricio Beltran9 years ago47this.hasInitialized = false;
48// Constructor is slim by design. This is to add as less computation as possible
49// to the initialization of the extension. If a public method is added, make sure
b0af599cJimmy Thomson9 years ago50// to call this.lazilyInitialize() at the begining of the code to be sure all variables
a57e740bPatricio Beltran9 years ago51// are correctly initialized.
1c32fe84Patricio Beltran9 years ago52}
53
ce5e88eeYuri Skorokhodov5 years ago54public configureExponentEnvironment(): Promise<void> {
d1d77244Jimmy Thomson9 years ago55this.lazilyInitialize();
34472878RedMickey5 years ago56this.logger.info(
57localize(
58"MakingSureYourProjectUsesCorrectExponentDependencies",
59"Making sure your project uses the correct dependencies for Expo. This may take a while...",
60),
61);
d7d405aeYuri Skorokhodov7 years ago62this.logger.logStream(localize("CheckingIfThisIsExpoApp", "Checking if this is Expo app."));
66412fdfRuslan Bikkinin7 years ago63let isExpo: boolean;
64return this.isExpoApp(true)
65.then(result => {
66isExpo = result;
67if (!isExpo) {
34472878RedMickey5 years ago68return this.appHasExpoInstalled().then(expoInstalled => {
66412fdfRuslan Bikkinin7 years ago69if (!expoInstalled) {
70// Expo requires expo package to be installed inside RN application in order to be able to run it
71// https://github.com/expo/expo-cli/issues/255#issuecomment-453214632
72this.logger.logStream("\n");
34472878RedMickey5 years ago73this.logger.logStream(
74localize(
75"ExpoPackageIsNotInstalled",
76'[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.',
77),
78);
66412fdfRuslan Bikkinin7 years ago79this.logger.logStream("\n");
80}
81});
82}
83return;
34472878RedMickey5 years ago84})
85.then(() => {
0a68f8dbArtem Egorov8 years ago86this.logger.logStream(".\n");
94cd5149Artem Egorov8 years ago87return this.patchAppJson(isExpo);
88});
d1d77244Jimmy Thomson9 years ago89}
90
91/**
92* Returns the current user. If there is none, asks user for username and password and logins to exponent servers.
93*/
5c8365a6Artem Egorov8 years ago94public loginToExponent(
ce5e88eeYuri Skorokhodov5 years ago95promptForInformation: (message: string, password: boolean) => Promise<string>,
34472878RedMickey5 years ago96showMessage: (message: string) => Promise<string>,
ce5e88eeYuri Skorokhodov5 years ago97): Promise<XDL.IUser> {
d1d77244Jimmy Thomson9 years ago98this.lazilyInitialize();
99return XDL.currentUser()
34472878RedMickey5 years ago100.then(user => {
d1d77244Jimmy Thomson9 years ago101if (!user) {
102let username = "";
34472878RedMickey5 years ago103return showMessage(
104localize(
105"YouNeedToLoginToExpo",
106"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.",
107),
108)
d1d77244Jimmy Thomson9 years ago109.then(() =>
34472878RedMickey5 years ago110promptForInformation(localize("ExpoUsername", "Expo username"), false),
111)
112.then((name: string) => {
d1d77244Jimmy Thomson9 years ago113username = name;
34472878RedMickey5 years ago114return promptForInformation(
115localize("ExpoPassword", "Expo password"),
116true,
117);
d1d77244Jimmy Thomson9 years ago118})
34472878RedMickey5 years ago119.then((password: string) => XDL.login(username, password));
d1d77244Jimmy Thomson9 years ago120}
121return user;
122})
123.catch(error => {
ce5e88eeYuri Skorokhodov5 years ago124return Promise.reject<XDL.IUser>(error);
d1d77244Jimmy Thomson9 years ago125});
126}
127
ce5e88eeYuri Skorokhodov5 years ago128public getExpPackagerOptions(): Promise<ExpConfigPackager> {
6458f408Nikita Matrosov9 years ago129this.lazilyInitialize();
34472878RedMickey5 years ago130return this.getFromExpConfig("packagerOpts").then(opts => opts || {});
6458f408Nikita Matrosov9 years ago131}
132
ce5e88eeYuri Skorokhodov5 years ago133public appHasExpoInstalled(): Promise<boolean> {
34472878RedMickey5 years ago134return this.getAppPackageInformation().then((packageJson: IPackageInformation) => {
135if (packageJson.dependencies && packageJson.dependencies.expo) {
136this.logger.debug(
137"'expo' package is found in 'dependencies' section of package.json",
138);
139return true;
140} else if (packageJson.devDependencies && packageJson.devDependencies.expo) {
141this.logger.debug(
142"'expo' package is found in 'devDependencies' section of package.json",
143);
144return true;
145}
146return false;
147});
66412fdfRuslan Bikkinin7 years ago148}
149
ce5e88eeYuri Skorokhodov5 years ago150public appHasExpoRNSDKInstalled(): Promise<boolean> {
34472878RedMickey5 years ago151return this.getAppPackageInformation().then((packageJson: IPackageInformation) => {
152const reactNativeValue: string | undefined =
153packageJson.dependencies && packageJson.dependencies["react-native"];
154if (reactNativeValue) {
155this.logger.debug(
156`'react-native' package with value '${reactNativeValue}' is found in 'dependencies' section of package.json`,
157);
158if (
159reactNativeValue.startsWith("https://github.com/expo/react-native/archive/sdk")
160) {
161return true;
66412fdfRuslan Bikkinin7 years ago162}
34472878RedMickey5 years ago163}
164return false;
165});
66412fdfRuslan Bikkinin7 years ago166}
167
ce5e88eeYuri Skorokhodov5 years ago168public isExpoApp(showProgress: boolean = false): Promise<boolean> {
db6fd42aRuslan Bikkinin7 years ago169if (showProgress) {
170this.logger.logStream("...");
171}
172
34472878RedMickey5 years ago173return Promise.all([this.appHasExpoInstalled(), this.appHasExpoRNSDKInstalled()])
174.then(([expoInstalled, expoRNSDKInstalled]) => {
ce5e88eeYuri Skorokhodov5 years ago175if (showProgress) this.logger.logStream(".");
176return expoInstalled && expoRNSDKInstalled;
34472878RedMickey5 years ago177})
178.catch(e => {
66412fdfRuslan Bikkinin7 years ago179this.logger.error(e.message, e, e.stack);
db6fd42aRuslan Bikkinin7 years ago180if (showProgress) {
181this.logger.logStream(".");
182}
183// Not in a react-native project
184return false;
185});
186}
187
1c32fe84Patricio Beltran9 years ago188/**
94cd5149Artem Egorov8 years ago189* Path to a given file inside the .vscode directory
1c32fe84Patricio Beltran9 years ago190*/
66412fdfRuslan Bikkinin7 years ago191private dotvscodePath(filename: string, isAbsolute: boolean): string {
192let paths = [".vscode", filename];
193if (isAbsolute) {
194paths = [this.workspaceRootPath].concat(...paths);
195}
196return path.join(...paths);
94cd5149Artem Egorov8 years ago197}
198
ce5e88eeYuri Skorokhodov5 years ago199private createExpoEntry(name: string): Promise<void> {
b0af599cJimmy Thomson9 years ago200this.lazilyInitialize();
34472878RedMickey5 years ago201return this.detectEntry().then((entryPoint: string) => {
202const content = this.generateFileContent(name, entryPoint);
203return this.fs.writeFile(this.dotvscodePath(EXPONENT_INDEX, true), content);
204});
94cd5149Artem Egorov8 years ago205}
1c32fe84Patricio Beltran9 years ago206
ce5e88eeYuri Skorokhodov5 years ago207private detectEntry(): Promise<string> {
94cd5149Artem Egorov8 years ago208this.lazilyInitialize();
ce5e88eeYuri Skorokhodov5 years ago209return Promise.all([
94cd5149Artem Egorov8 years ago210this.fs.exists(this.pathToFileInWorkspace(DEFAULT_EXPONENT_INDEX)),
211this.fs.exists(this.pathToFileInWorkspace(DEFAULT_IOS_INDEX)),
212this.fs.exists(this.pathToFileInWorkspace(DEFAULT_ANDROID_INDEX)),
ce5e88eeYuri Skorokhodov5 years ago213]).then(([expo, ios]) => {
34472878RedMickey5 years ago214return expo
215? this.pathToFileInWorkspace(DEFAULT_EXPONENT_INDEX)
216: ios
217? this.pathToFileInWorkspace(DEFAULT_IOS_INDEX)
218: this.pathToFileInWorkspace(DEFAULT_ANDROID_INDEX);
ce5e88eeYuri Skorokhodov5 years ago219});
94cd5149Artem Egorov8 years ago220}
1c32fe84Patricio Beltran9 years ago221
94cd5149Artem Egorov8 years ago222private generateFileContent(name: string, entryPoint: string): string {
223return `// This file is automatically generated by VS Code
224// Please do not modify it manually. All changes will be lost.
225var React = require('${this.pathToFileInWorkspace("/node_modules/react")}');
226var { Component } = React;
227var ReactNative = require('${this.pathToFileInWorkspace("/node_modules/react-native")}');
228var { AppRegistry } = ReactNative;
f6b41bbfAlexander Sorokin9 years ago229AppRegistry.registerRunnable('main', function(appParameters) {
94cd5149Artem Egorov8 years ago230AppRegistry.runApplication('${name}', appParameters);
253b6e8eRedMickey5 years ago231});
232var entryPoint = require('${entryPoint}');`;
1c32fe84Patricio Beltran9 years ago233}
234
ce5e88eeYuri Skorokhodov5 years ago235private patchAppJson(isExpo: boolean = true): Promise<void> {
94cd5149Artem Egorov8 years ago236return this.readAppJson()
237.catch(() => {
238// if app.json doesn't exist but it's ok, we will create it
239return {};
240})
241.then((config: AppJson) => {
242let expoConfig = <ExpConfig>(config.expo || {});
243if (!expoConfig.name || !expoConfig.slug) {
34472878RedMickey5 years ago244return this.getPackageName().then((name: string) => {
245expoConfig.slug = expoConfig.slug || config.name || name.replace(" ", "-");
246expoConfig.name = expoConfig.name || config.name || name;
247config.expo = expoConfig;
248return config;
249});
b0af599cJimmy Thomson9 years ago250}
251
94cd5149Artem Egorov8 years ago252return config;
253})
66412fdfRuslan Bikkinin7 years ago254.then((config: AppJson) => {
255if (!config.name) {
34472878RedMickey5 years ago256return this.getPackageName().then((name: string) => {
257config.name = name;
258return config;
259});
66412fdfRuslan Bikkinin7 years ago260}
261
262return config;
263})
94cd5149Artem Egorov8 years ago264.then((config: AppJson) => {
265if (!config.expo.sdkVersion) {
34472878RedMickey5 years ago266return this.exponentSdk(true).then(sdkVersion => {
267config.expo.sdkVersion = sdkVersion;
268return config;
269});
1c32fe84Patricio Beltran9 years ago270}
66412fdfRuslan Bikkinin7 years ago271
94cd5149Artem Egorov8 years ago272return config;
1c32fe84Patricio Beltran9 years ago273})
94cd5149Artem Egorov8 years ago274.then((config: AppJson) => {
275if (!isExpo) {
66412fdfRuslan Bikkinin7 years ago276// entryPoint must be relative
277// https://docs.expo.io/versions/latest/workflow/configuration/#entrypoint
278config.expo.entryPoint = this.dotvscodePath(EXPONENT_INDEX, false);
94cd5149Artem Egorov8 years ago279}
1c32fe84Patricio Beltran9 years ago280
94cd5149Artem Egorov8 years ago281return config;
282})
283.then((config: AppJson) => {
5c8365a6Artem Egorov8 years ago284return config ? this.writeAppJson(config) : config;
1c32fe84Patricio Beltran9 years ago285})
94cd5149Artem Egorov8 years ago286.then((config: AppJson) => {
ce5e88eeYuri Skorokhodov5 years ago287return isExpo ? Promise.resolve() : this.createExpoEntry(config.expo.name);
1c32fe84Patricio Beltran9 years ago288});
27710197Vladimir Kotikov8 years ago289}
1c32fe84Patricio Beltran9 years ago290
291/**
292* Exponent sdk version that maps to the current react-native version
293* If react native version is not supported it returns null.
294*/
ce5e88eeYuri Skorokhodov5 years ago295private exponentSdk(showProgress: boolean = false): Promise<string> {
94cd5149Artem Egorov8 years ago296if (showProgress) {
0a68f8dbArtem Egorov8 years ago297this.logger.logStream("...");
1c32fe84Patricio Beltran9 years ago298}
94cd5149Artem Egorov8 years ago299
34472878RedMickey5 years ago300return ProjectVersionHelper.getReactNativeVersions(this.projectRootPath).then(versions => {
301if (showProgress) this.logger.logStream(".");
302return XDL.mapVersion(versions.reactNativeVersion).then(sdkVersion => {
303if (!sdkVersion) {
304return XDL.supportedVersions().then(versions => {
305return Promise.reject<string>(
306ErrorHelper.getInternalError(
307InternalErrorCode.RNVersionNotSupportedByExponent,
308versions.join(", "),
309),
310);
1c32fe84Patricio Beltran9 years ago311});
34472878RedMickey5 years ago312}
313return sdkVersion;
1c32fe84Patricio Beltran9 years ago314});
34472878RedMickey5 years ago315});
1c32fe84Patricio Beltran9 years ago316}
317
318/**
94cd5149Artem Egorov8 years ago319* Name specified on user's package.json
1c32fe84Patricio Beltran9 years ago320*/
ce5e88eeYuri Skorokhodov5 years ago321private getPackageName(): Promise<string> {
94cd5149Artem Egorov8 years ago322return new Package(this.projectRootPath, { fileSystem: this.fs }).name();
323}
324
ce5e88eeYuri Skorokhodov5 years ago325private getExpConfig(): Promise<ExpConfig> {
34472878RedMickey5 years ago326return this.readExpJson().catch(err => {
327if (err.code === "ENOENT") {
328return this.readAppJson().then((config: AppJson) => {
329return config.expo || {};
330});
331}
332
333return err;
334});
1c32fe84Patricio Beltran9 years ago335}
336
ce5e88eeYuri Skorokhodov5 years ago337private getFromExpConfig(key: string): Promise<any> {
34472878RedMickey5 years ago338return this.getExpConfig().then((config: ExpConfig) => config[key]);
1c32fe84Patricio Beltran9 years ago339}
340
341/**
94cd5149Artem Egorov8 years ago342* Returns the specified setting from exp.json if it exists
1c32fe84Patricio Beltran9 years ago343*/
ce5e88eeYuri Skorokhodov5 years ago344private readExpJson(): Promise<ExpConfig> {
94cd5149Artem Egorov8 years ago345const expJsonPath = this.pathToFileInWorkspace(EXP_JSON);
34472878RedMickey5 years ago346return this.fs.readFile(expJsonPath).then(content => {
347return JSON.parse(stripJSONComments(content.toString()));
348});
1c32fe84Patricio Beltran9 years ago349}
350
ce5e88eeYuri Skorokhodov5 years ago351private readAppJson(): Promise<AppJson> {
94cd5149Artem Egorov8 years ago352const appJsonPath = this.pathToFileInWorkspace(APP_JSON);
34472878RedMickey5 years ago353return this.fs.readFile(appJsonPath).then(content => {
354return JSON.parse(stripJSONComments(content.toString()));
355});
1c32fe84Patricio Beltran9 years ago356}
357
ce5e88eeYuri Skorokhodov5 years ago358private writeAppJson(config: AppJson): Promise<AppJson> {
94cd5149Artem Egorov8 years ago359const appJsonPath = this.pathToFileInWorkspace(APP_JSON);
34472878RedMickey5 years ago360return this.fs.writeFile(appJsonPath, JSON.stringify(config, null, 2)).then(() => config);
1c32fe84Patricio Beltran9 years ago361}
362
ce5e88eeYuri Skorokhodov5 years ago363private getAppPackageInformation(): Promise<IPackageInformation> {
66412fdfRuslan Bikkinin7 years ago364return new Package(this.projectRootPath, { fileSystem: this.fs }).parsePackageInformation();
365}
366
1c32fe84Patricio Beltran9 years ago367/**
38edb09eAlexander Sorokin9 years ago368* Path to a given file from the workspace root
1c32fe84Patricio Beltran9 years ago369*/
370private pathToFileInWorkspace(filename: string): string {
94cd5149Artem Egorov8 years ago371return path.join(this.projectRootPath, filename).replace(DBL_SLASHES, "/");
1c32fe84Patricio Beltran9 years ago372}
373
a57e740bPatricio Beltran9 years ago374/**
375* Works as a constructor but only initiliazes when it's actually needed.
376*/
b0af599cJimmy Thomson9 years ago377private lazilyInitialize(): void {
a57e740bPatricio Beltran9 years ago378if (!this.hasInitialized) {
379this.hasInitialized = true;
380
381XDL.configReactNativeVersionWargnings();
38edb09eAlexander Sorokin9 years ago382XDL.attachLoggerStream(this.projectRootPath, {
a57e740bPatricio Beltran9 years ago383stream: {
384write: (chunk: any) => {
385if (chunk.level <= 30) {
0a68f8dbArtem Egorov8 years ago386this.logger.logStream(chunk.msg);
a57e740bPatricio Beltran9 years ago387} else if (chunk.level === 40) {
0a68f8dbArtem Egorov8 years ago388this.logger.warning(chunk.msg);
a57e740bPatricio Beltran9 years ago389} else {
0a68f8dbArtem Egorov8 years ago390this.logger.error(chunk.msg);
a57e740bPatricio Beltran9 years ago391}
392},
393},
394type: "raw",
395});
396}
397}
5c8365a6Artem Egorov8 years ago398}