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