microsoft/vscode-react-native
Publicmirrored from https://github.com/microsoft/vscode-react-nativeAvailable
src/extension/exponent/exponentHelper.ts
542lines · 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 | | |
09f6024fHeniker4 years ago | 4 | // eslint-disable-next-line @typescript-eslint/triple-slash-reference |
94cd5149Artem Egorov8 years ago | 5 | /// <reference path="exponentHelper.d.ts" /> |
| 6 | | |
1c32fe84Patricio Beltran9 years ago | 7 | import * as path from "path"; |
57fee98eEzio Li3 years ago | 8 | import * as fs from "fs"; |
8f50947fRedMickey5 years ago | 9 | import * as semver from "semver"; |
efb436fcRedMickey5 years ago | 10 | import * as vscode from "vscode"; |
866e020bRedMickey4 years ago | 11 | import { sync as globSync } from "glob"; |
d7d405aeYuri Skorokhodov7 years ago | 12 | import * as nls from "vscode-nls"; |
623be8a6Ezio Li2 years ago | 13 | import { logger } from "@vscode/debugadapter"; |
dc94981bQuan Jin3 years ago | 14 | import { stripJsonTrailingComma, getNodeModulesGlobalPath } from "../../common/utils"; |
66412fdfRuslan Bikkinin7 years ago | 15 | import { Package, IPackageInformation } from "../../common/node/package"; |
e3706a1cRedMickey6 years ago | 16 | import { ProjectVersionHelper } from "../../common/projectVersionHelper"; |
34472878RedMickey5 years ago | 17 | import { OutputChannelLogger } from "../log/OutputChannelLogger"; |
d7d405aeYuri Skorokhodov7 years ago | 18 | import { ErrorHelper } from "../../common/error/errorHelper"; |
892f0aa6JiglioNero4 years ago | 19 | import { PackageLoader, PackageConfig } from "../../common/packageLoader"; |
d7d405aeYuri Skorokhodov7 years ago | 20 | import { InternalErrorCode } from "../../common/error/internalErrorCode"; |
ce5e88eeYuri Skorokhodov5 years ago | 21 | import { FileSystem } from "../../common/node/fileSystem"; |
892f0aa6JiglioNero4 years ago | 22 | import { SettingsHelper } from "../settingsHelper"; |
09f6024fHeniker4 years ago | 23 | import * as XDL from "./xdlInterface"; |
| 24 | | |
34472878RedMickey5 years ago | 25 | nls.config({ |
| 26 | messageFormat: nls.MessageFormat.bundle, | |
| 27 | bundleFormat: nls.BundleFormat.standalone, | |
| 28 | })(); | |
d7d405aeYuri Skorokhodov7 years ago | 29 | const localize = nls.loadMessageBundle(); |
1c32fe84Patricio Beltran9 years ago | 30 | |
94cd5149Artem Egorov8 years ago | 31 | const APP_JSON = "app.json"; |
| 32 | const EXP_JSON = "exp.json"; | |
1c32fe84Patricio Beltran9 years ago | 33 | |
| 34 | const EXPONENT_INDEX = "exponentIndex.js"; | |
94cd5149Artem Egorov8 years ago | 35 | const DEFAULT_EXPONENT_INDEX = "index.js"; |
1c32fe84Patricio Beltran9 years ago | 36 | const DEFAULT_IOS_INDEX = "index.ios.js"; |
| 37 | const DEFAULT_ANDROID_INDEX = "index.android.js"; | |
| 38 | | |
94cd5149Artem Egorov8 years ago | 39 | const DBL_SLASHES = /\\/g; |
1c32fe84Patricio Beltran9 years ago | 40 | |
efb436fcRedMickey5 years ago | 41 | const NGROK_PACKAGE = "@expo/ngrok"; |
| 42 | | |
1c32fe84Patricio Beltran9 years ago | 43 | export class ExponentHelper { |
38edb09eAlexander Sorokin9 years ago | 44 | private workspaceRootPath: string; |
94cd5149Artem Egorov8 years ago | 45 | private projectRootPath: string; |
2956dba4Yuri Skorokhodov7 years ago | 46 | private fs: FileSystem; |
a57e740bPatricio Beltran9 years ago | 47 | private hasInitialized: boolean; |
efb436fcRedMickey5 years ago | 48 | private nodeModulesGlobalPathAddedToEnv: boolean; |
0a68f8dbArtem Egorov8 years ago | 49 | private logger: OutputChannelLogger = OutputChannelLogger.getMainChannel(); |
1c32fe84Patricio Beltran9 years ago | 50 | |
34472878RedMickey5 years ago | 51 | public constructor( |
| 52 | workspaceRootPath: string, | |
| 53 | projectRootPath: string, | |
| 54 | fs: FileSystem = new FileSystem(), | |
| 55 | ) { | |
38edb09eAlexander Sorokin9 years ago | 56 | this.workspaceRootPath = workspaceRootPath; |
| 57 | this.projectRootPath = projectRootPath; | |
2956dba4Yuri Skorokhodov7 years ago | 58 | this.fs = fs; |
a57e740bPatricio Beltran9 years ago | 59 | this.hasInitialized = false; |
| 60 | // Constructor is slim by design. This is to add as less computation as possible | |
| 61 | // to the initialization of the extension. If a public method is added, make sure | |
dc94981bQuan Jin3 years ago | 62 | // to call this.lazilyInitialize() at the beginning of the code to be sure all variables |
a57e740bPatricio Beltran9 years ago | 63 | // are correctly initialized. |
efb436fcRedMickey5 years ago | 64 | this.nodeModulesGlobalPathAddedToEnv = false; |
1c32fe84Patricio Beltran9 years ago | 65 | } |
| 66 | | |
716f31d0JiglioNero5 years ago | 67 | public async preloadExponentDependency(): Promise<[typeof xdl, typeof metroConfig]> { |
34472878RedMickey5 years ago | 68 | this.logger.info( |
| 69 | localize( | |
| 70 | "MakingSureYourProjectUsesCorrectExponentDependencies", | |
| 71 | "Making sure your project uses the correct dependencies for Expo. This may take a while...", | |
| 72 | ), | |
| 73 | ); | |
716f31d0JiglioNero5 years ago | 74 | return Promise.all([XDL.getXDLPackage(), XDL.getMetroConfigPackage()]); |
| 75 | } | |
| 76 | | |
0d77292aJiglioNero4 years ago | 77 | public async configureExponentEnvironment(): Promise<void> { |
| 78 | await this.lazilyInitialize(); | |
| 79 | this.logger.logStream( | |
| 80 | localize("CheckingIfThisIsExpoApp", "Checking if this is an Expo app."), | |
| 81 | ); | |
3cf8f692Ezio Li3 years ago | 82 | this.logger.logStream("\n"); |
| 83 | | |
| 84 | const packageJson = await this.getAppPackageInformation(); | |
| 85 | if (!packageJson.name || !packageJson.version) { | |
| 86 | this.logger.warning( | |
| 87 | localize( | |
| 88 | "MissingFieldsInExpoApp", | |
| 89 | "Missing 'name' or 'version' field in package.json. These fields might be required for your application.", | |
| 90 | ), | |
| 91 | ); | |
| 92 | } | |
| 93 | | |
09f6024fHeniker4 years ago | 94 | const isExpo = await this.isExpoManagedApp(true); |
0d77292aJiglioNero4 years ago | 95 | if (!isExpo) { |
| 96 | if (!(await this.appHasExpoInstalled())) { | |
| 97 | // Expo requires expo package to be installed inside RN application in order to be able to run it | |
| 98 | // https://github.com/expo/expo-cli/issues/255#issuecomment-453214632 | |
| 99 | this.logger.logStream("\n"); | |
716f31d0JiglioNero5 years ago | 100 | this.logger.logStream( |
0d77292aJiglioNero4 years ago | 101 | localize( |
| 102 | "ExpoPackageIsNotInstalled", | |
| 103 | '[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.', | |
| 104 | ), | |
716f31d0JiglioNero5 years ago | 105 | ); |
0d77292aJiglioNero4 years ago | 106 | this.logger.logStream("\n"); |
| 107 | } | |
| 108 | } | |
| 109 | this.logger.logStream(".\n"); | |
| 110 | await this.patchAppJson(isExpo); | |
d1d77244Jimmy Thomson9 years ago | 111 | } |
| 112 | | |
| 113 | /** | |
| 114 | * Returns the current user. If there is none, asks user for username and password and logins to exponent servers. | |
| 115 | */ | |
0d77292aJiglioNero4 years ago | 116 | public async loginToExponent( |
ce5e88eeYuri Skorokhodov5 years ago | 117 | promptForInformation: (message: string, password: boolean) => Promise<string>, |
34472878RedMickey5 years ago | 118 | showMessage: (message: string) => Promise<string>, |
ce5e88eeYuri Skorokhodov5 years ago | 119 | ): Promise<XDL.IUser> { |
0d77292aJiglioNero4 years ago | 120 | await this.lazilyInitialize(); |
| 121 | let user = await XDL.currentUser(); | |
| 122 | if (!user) { | |
| 123 | await showMessage( | |
| 124 | localize( | |
| 125 | "YouNeedToLoginToExpo", | |
| 126 | "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.", | |
| 127 | ), | |
| 128 | ); | |
| 129 | const username = await promptForInformation( | |
| 130 | localize("ExpoUsername", "Expo username"), | |
| 131 | false, | |
| 132 | ); | |
| 133 | const password = await promptForInformation( | |
| 134 | localize("ExpoPassword", "Expo password"), | |
| 135 | true, | |
| 136 | ); | |
| 137 | user = await XDL.login(username, password); | |
| 138 | } | |
| 139 | return user; | |
6458f408Nikita Matrosov9 years ago | 140 | } |
| 141 | | |
242759feJiglioNero5 years ago | 142 | public async getExpPackagerOptions(projectRoot: string): Promise<ExpMetroConfig> { |
716f31d0JiglioNero5 years ago | 143 | await this.lazilyInitialize(); |
242759feJiglioNero5 years ago | 144 | const options = await this.getFromExpConfig<any>("packagerOpts").then(opts => opts || {}); |
| 145 | const metroConfig = await this.getArgumentsFromExpoMetroConfig(projectRoot); | |
| 146 | return { ...options, ...metroConfig }; | |
66412fdfRuslan Bikkinin7 years ago | 147 | } |
| 148 | | |
0d77292aJiglioNero4 years ago | 149 | public async appHasExpoInstalled(): Promise<boolean> { |
| 150 | const packageJson = await this.getAppPackageInformation(); | |
| 151 | if (packageJson.dependencies && packageJson.dependencies.expo) { | |
| 152 | this.logger.debug("'expo' package is found in 'dependencies' section of package.json"); | |
| 153 | return true; | |
| 154 | } else if (packageJson.devDependencies && packageJson.devDependencies.expo) { | |
| 155 | this.logger.debug( | |
| 156 | "'expo' package is found in 'devDependencies' section of package.json", | |
| 157 | ); | |
| 158 | return true; | |
| 159 | } | |
| 160 | return false; | |
66412fdfRuslan Bikkinin7 years ago | 161 | } |
| 162 | | |
866e020bRedMickey4 years ago | 163 | public async isExpoManagedApp(showProgress: boolean = false): Promise<boolean> { |
db6fd42aRuslan Bikkinin7 years ago | 164 | if (showProgress) { |
| 165 | this.logger.logStream("..."); | |
| 166 | } | |
| 167 | | |
0d77292aJiglioNero4 years ago | 168 | try { |
866e020bRedMickey4 years ago | 169 | const expoInstalled = await this.appHasExpoInstalled(); |
| 170 | if (!expoInstalled) return false; | |
| 171 | | |
| 172 | const isBareWorkflowProject = await this.isBareWorkflowProject(); | |
0d77292aJiglioNero4 years ago | 173 | if (showProgress) this.logger.logStream("."); |
866e020bRedMickey4 years ago | 174 | return !isBareWorkflowProject; |
0d77292aJiglioNero4 years ago | 175 | } catch (e) { |
| 176 | this.logger.error(e.message, e, e.stack); | |
| 177 | if (showProgress) { | |
| 178 | this.logger.logStream("."); | |
| 179 | } | |
| 180 | // Not in a react-native project | |
| 181 | return false; | |
| 182 | } | |
db6fd42aRuslan Bikkinin7 years ago | 183 | } |
| 184 | | |
0d77292aJiglioNero4 years ago | 185 | public async findOrInstallNgrokGlobally(): Promise<void> { |
| 186 | let ngrokInstalled: boolean; | |
| 187 | try { | |
| 188 | await this.addNodeModulesPathToEnvIfNotPresent(); | |
| 189 | ngrokInstalled = await XDL.isNgrokInstalled(this.projectRootPath); | |
| 190 | } catch (e) { | |
| 191 | ngrokInstalled = false; | |
| 192 | } | |
| 193 | if (!ngrokInstalled) { | |
c0dc4020etatanova4 years ago | 194 | const ngrokVersion = SettingsHelper.getExpoDependencyVersion("@expo/ngrok"); |
0d77292aJiglioNero4 years ago | 195 | const ngrokPackageConfig = new PackageConfig(NGROK_PACKAGE, ngrokVersion); |
| 196 | | |
| 197 | const outputMessage = localize( | |
| 198 | "ExpoInstallNgrokGlobally", | |
| 199 | 'It seems that "{0}" package isn\'t installed globally. This package is required to use Expo tunnels, would you like to install it globally?', | |
| 200 | ngrokPackageConfig.getStringForInstall(), | |
| 201 | ); | |
| 202 | const installButton = localize("InstallNgrokGloballyButtonOK", "Install"); | |
| 203 | const cancelButton = localize("InstallNgrokGloballyButtonCancel", "Cancel"); | |
| 204 | | |
| 205 | const selectedItem = await vscode.window.showWarningMessage( | |
| 206 | outputMessage, | |
| 207 | installButton, | |
| 208 | cancelButton, | |
| 209 | ); | |
| 210 | if (selectedItem === installButton) { | |
| 211 | await PackageLoader.getInstance().installGlobalPackage( | |
| 212 | ngrokPackageConfig, | |
| 213 | this.projectRootPath, | |
| 214 | ); | |
| 215 | this.logger.info( | |
| 216 | localize( | |
| 217 | "NgrokInstalledGlobally", | |
| 218 | '"{0}" package has been successfully installed globally.', | |
0690ab22RedMickey4 years ago | 219 | ngrokPackageConfig.getStringForInstall(), |
0d77292aJiglioNero4 years ago | 220 | ), |
| 221 | ); | |
| 222 | } else { | |
| 223 | throw ErrorHelper.getInternalError( | |
| 224 | InternalErrorCode.NgrokIsNotInstalledGlobally, | |
| 225 | ngrokPackageConfig.getVersion(true), | |
| 226 | ); | |
| 227 | } | |
| 228 | } | |
efb436fcRedMickey5 years ago | 229 | } |
| 230 | | |
| 231 | public removeNodeModulesPathFromEnvIfWasSet(): void { | |
| 232 | if (this.nodeModulesGlobalPathAddedToEnv) { | |
09f6024fHeniker4 years ago | 233 | delete process.env.NODE_MODULES; |
efb436fcRedMickey5 years ago | 234 | this.nodeModulesGlobalPathAddedToEnv = false; |
| 235 | } | |
| 236 | } | |
| 237 | | |
0d77292aJiglioNero4 years ago | 238 | public async addNodeModulesPathToEnvIfNotPresent(): Promise<void> { |
09f6024fHeniker4 years ago | 239 | if (!process.env.NODE_MODULES) { |
| 240 | process.env.NODE_MODULES = await getNodeModulesGlobalPath(); | |
0d77292aJiglioNero4 years ago | 241 | this.nodeModulesGlobalPathAddedToEnv = true; |
efb436fcRedMickey5 years ago | 242 | } |
| 243 | } | |
| 244 | | |
866e020bRedMickey4 years ago | 245 | private async isBareWorkflowProject(): Promise<boolean> { |
| 246 | const packageJson = await this.getAppPackageInformation(); | |
| 247 | | |
| 248 | if (packageJson.dependencies && packageJson.dependencies.expokit) { | |
| 249 | return false; | |
| 250 | } | |
| 251 | if (packageJson.devDependencies && packageJson.devDependencies.expokit) { | |
| 252 | return false; | |
| 253 | } | |
| 254 | | |
| 255 | const xcodeprojFiles = globSync("ios/**/*.xcodeproj", { | |
| 256 | absolute: true, | |
| 257 | cwd: this.projectRootPath, | |
| 258 | }); | |
| 259 | if (xcodeprojFiles.length) { | |
| 260 | return true; | |
| 261 | } | |
| 262 | const gradleFiles = globSync("android/**/*.gradle", { | |
| 263 | absolute: true, | |
| 264 | cwd: this.projectRootPath, | |
| 265 | }); | |
| 266 | if (gradleFiles.length) { | |
| 267 | return true; | |
| 268 | } | |
| 269 | | |
| 270 | return false; | |
| 271 | } | |
| 272 | | |
efb436fcRedMickey5 years ago | 273 | private async getArgumentsFromExpoMetroConfig(projectRoot: string): Promise<ExpMetroConfig> { |
0d77292aJiglioNero4 years ago | 274 | const config = await XDL.getMetroConfig(projectRoot); |
| 275 | return { sourceExts: config.resolver.sourceExts }; | |
db6fd42aRuslan Bikkinin7 years ago | 276 | } |
| 277 | | |
1c32fe84Patricio Beltran9 years ago | 278 | /** |
94cd5149Artem Egorov8 years ago | 279 | * Path to a given file inside the .vscode directory |
1c32fe84Patricio Beltran9 years ago | 280 | */ |
66412fdfRuslan Bikkinin7 years ago | 281 | private dotvscodePath(filename: string, isAbsolute: boolean): string { |
| 282 | let paths = [".vscode", filename]; | |
| 283 | if (isAbsolute) { | |
| 284 | paths = [this.workspaceRootPath].concat(...paths); | |
| 285 | } | |
| 286 | return path.join(...paths); | |
94cd5149Artem Egorov8 years ago | 287 | } |
| 288 | | |
0d77292aJiglioNero4 years ago | 289 | private async createExpoEntry(name: string): Promise<void> { |
| 290 | await this.lazilyInitialize(); | |
| 291 | const entryPoint = await this.detectEntry(); | |
| 292 | const content = this.generateFileContent(name, entryPoint); | |
| 293 | return await this.fs.writeFile(this.dotvscodePath(EXPONENT_INDEX, true), content); | |
94cd5149Artem Egorov8 years ago | 294 | } |
1c32fe84Patricio Beltran9 years ago | 295 | |
0d77292aJiglioNero4 years ago | 296 | private async detectEntry(): Promise<string> { |
| 297 | await this.lazilyInitialize(); | |
| 298 | const [expo, ios] = await Promise.all([ | |
94cd5149Artem Egorov8 years ago | 299 | this.fs.exists(this.pathToFileInWorkspace(DEFAULT_EXPONENT_INDEX)), |
| 300 | this.fs.exists(this.pathToFileInWorkspace(DEFAULT_IOS_INDEX)), | |
| 301 | this.fs.exists(this.pathToFileInWorkspace(DEFAULT_ANDROID_INDEX)), | |
0d77292aJiglioNero4 years ago | 302 | ]); |
| 303 | return expo | |
| 304 | ? this.pathToFileInWorkspace(DEFAULT_EXPONENT_INDEX) | |
| 305 | : ios | |
| 306 | ? this.pathToFileInWorkspace(DEFAULT_IOS_INDEX) | |
| 307 | : this.pathToFileInWorkspace(DEFAULT_ANDROID_INDEX); | |
94cd5149Artem Egorov8 years ago | 308 | } |
1c32fe84Patricio Beltran9 years ago | 309 | |
94cd5149Artem Egorov8 years ago | 310 | private generateFileContent(name: string, entryPoint: string): string { |
| 311 | return `// This file is automatically generated by VS Code | |
| 312 | // Please do not modify it manually. All changes will be lost. | |
| 313 | var React = require('${this.pathToFileInWorkspace("/node_modules/react")}'); | |
| 314 | var { Component } = React; | |
| 315 | var ReactNative = require('${this.pathToFileInWorkspace("/node_modules/react-native")}'); | |
| 316 | var { AppRegistry } = ReactNative; | |
f6b41bbfAlexander Sorokin9 years ago | 317 | AppRegistry.registerRunnable('main', function(appParameters) { |
94cd5149Artem Egorov8 years ago | 318 | AppRegistry.runApplication('${name}', appParameters); |
253b6e8eRedMickey5 years ago | 319 | }); |
6a47f22aRedMickey4 years ago | 320 | require('${entryPoint}');`; |
1c32fe84Patricio Beltran9 years ago | 321 | } |
| 322 | | |
0d77292aJiglioNero4 years ago | 323 | private async patchAppJson(isExpo: boolean = true): Promise<void> { |
| 324 | let appJson: AppJson; | |
| 325 | try { | |
| 326 | appJson = await this.readAppJson(); | |
| 327 | } catch { | |
7888898eEzio Li3 years ago | 328 | // If app.json doesn't exist, we will create it |
| 329 | logger.log("Cannot get existing app.json file. Create new one."); | |
0d77292aJiglioNero4 years ago | 330 | appJson = <AppJson>{}; |
| 331 | } | |
| 332 | const packageName = await this.getPackageName(); | |
| 333 | | |
| 334 | const expoConfig = <ExpConfig>(appJson.expo || {}); | |
| 335 | if (!expoConfig.name || !expoConfig.slug) { | |
| 336 | expoConfig.slug = expoConfig.slug || appJson.name || packageName.replace(" ", "-"); | |
| 337 | expoConfig.name = expoConfig.name || appJson.name || packageName; | |
| 338 | appJson.expo = expoConfig; | |
| 339 | } | |
| 340 | | |
| 341 | if (!appJson.name) { | |
| 342 | appJson.name = packageName; | |
| 343 | } | |
| 344 | | |
| 345 | if (!appJson.expo.sdkVersion) { | |
| 346 | const sdkVersion = await this.exponentSdk(true); | |
| 347 | appJson.expo.sdkVersion = sdkVersion; | |
| 348 | } | |
| 349 | | |
| 350 | if (!isExpo) { | |
| 351 | // entryPoint must be relative | |
| 352 | // https://docs.expo.io/versions/latest/workflow/configuration/#entrypoint | |
| 353 | appJson.expo.entryPoint = this.dotvscodePath(EXPONENT_INDEX, false); | |
| 354 | } | |
| 355 | | |
| 356 | appJson = appJson ? await this.writeAppJson(appJson) : appJson; | |
| 357 | | |
| 358 | if (!isExpo) { | |
| 359 | await this.createExpoEntry(appJson.expo.name); | |
| 360 | } | |
27710197Vladimir Kotikov8 years ago | 361 | } |
1c32fe84Patricio Beltran9 years ago | 362 | |
| 363 | /** | |
| 364 | * Exponent sdk version that maps to the current react-native version | |
| 365 | * If react native version is not supported it returns null. | |
| 366 | */ | |
a1824f64Ezio Li2 years ago | 367 | public async exponentSdk(showProgress: boolean = false): Promise<string> { |
94cd5149Artem Egorov8 years ago | 368 | if (showProgress) { |
0a68f8dbArtem Egorov8 years ago | 369 | this.logger.logStream("..."); |
1c32fe84Patricio Beltran9 years ago | 370 | } |
94cd5149Artem Egorov8 years ago | 371 | |
0d77292aJiglioNero4 years ago | 372 | const versions = await ProjectVersionHelper.getReactNativeVersions(this.projectRootPath); |
| 373 | if (showProgress) { | |
| 374 | this.logger.logStream("."); | |
| 375 | } | |
| 376 | const sdkVersion = await this.mapFacebookReactNativeVersionToExpoVersion( | |
| 377 | versions.reactNativeVersion, | |
| 378 | ); | |
| 379 | if (!sdkVersion) { | |
| 380 | const supportedVersions = await this.getFacebookReactNativeVersions(); | |
| 381 | throw ErrorHelper.getInternalError( | |
| 382 | InternalErrorCode.RNVersionNotSupportedByExponent, | |
| 383 | supportedVersions.join(", "), | |
| 384 | ); | |
| 385 | } | |
| 386 | return sdkVersion; | |
1c32fe84Patricio Beltran9 years ago | 387 | } |
| 388 | | |
0d77292aJiglioNero4 years ago | 389 | private async getFacebookReactNativeVersions(): Promise<string[]> { |
| 390 | const sdkVersions = await XDL.getExpoSdkVersions(); | |
| 391 | const facebookReactNativeVersions = new Set( | |
| 392 | Object.values(sdkVersions) | |
| 393 | .map(data => data.facebookReactNativeVersion) | |
| 394 | .filter(version => version), | |
| 395 | ); | |
| 396 | return Array.from(facebookReactNativeVersions); | |
8f50947fRedMickey5 years ago | 397 | } |
| 398 | | |
0d77292aJiglioNero4 years ago | 399 | private async mapFacebookReactNativeVersionToExpoVersion( |
8f50947fRedMickey5 years ago | 400 | outerFacebookReactNativeVersion: string, |
| 401 | ): Promise<string | null> { | |
| 402 | if (!semver.valid(outerFacebookReactNativeVersion)) { | |
0d77292aJiglioNero4 years ago | 403 | throw new Error( |
| 404 | `${outerFacebookReactNativeVersion} is not a valid version. It must be in the form of x.y.z`, | |
8f50947fRedMickey5 years ago | 405 | ); |
| 406 | } | |
| 407 | | |
0d77292aJiglioNero4 years ago | 408 | const sdkVersions = await XDL.getReleasedExpoSdkVersions(); |
| 409 | let currentSdkVersion: string | null = null; | |
| 410 | for (const [version, { facebookReactNativeVersion }] of Object.entries(sdkVersions)) { | |
| 411 | if ( | |
| 412 | semver.major(outerFacebookReactNativeVersion) === | |
| 413 | semver.major(facebookReactNativeVersion) && | |
| 414 | semver.minor(outerFacebookReactNativeVersion) === | |
| 415 | semver.minor(facebookReactNativeVersion) && | |
| 416 | (!currentSdkVersion || semver.gt(version, currentSdkVersion)) | |
| 417 | ) { | |
| 418 | currentSdkVersion = version; | |
8f50947fRedMickey5 years ago | 419 | } |
0d77292aJiglioNero4 years ago | 420 | } |
| 421 | return currentSdkVersion; | |
8f50947fRedMickey5 years ago | 422 | } |
94cd5149Artem Egorov8 years ago | 423 | |
1c32fe84Patricio Beltran9 years ago | 424 | /** |
94cd5149Artem Egorov8 years ago | 425 | * Name specified on user's package.json |
1c32fe84Patricio Beltran9 years ago | 426 | */ |
ce5e88eeYuri Skorokhodov5 years ago | 427 | private getPackageName(): Promise<string> { |
94cd5149Artem Egorov8 years ago | 428 | return new Package(this.projectRootPath, { fileSystem: this.fs }).name(); |
| 429 | } | |
| 430 | | |
0d77292aJiglioNero4 years ago | 431 | private async getExpConfig(): Promise<ExpConfig> { |
| 432 | try { | |
| 433 | return this.readExpJson(); | |
| 434 | } catch (err) { | |
34472878RedMickey5 years ago | 435 | if (err.code === "ENOENT") { |
0d77292aJiglioNero4 years ago | 436 | const appJson = await this.readAppJson(); |
| 437 | return appJson.expo || {}; | |
34472878RedMickey5 years ago | 438 | } |
0d77292aJiglioNero4 years ago | 439 | throw err; |
| 440 | } | |
1c32fe84Patricio Beltran9 years ago | 441 | } |
| 442 | | |
69305188EzioLi9 months ago | 443 | private async getFromExpConfig<K extends keyof ExpConfig>(key: K): Promise<ExpConfig[K]> { |
0d77292aJiglioNero4 years ago | 444 | const config = await this.getExpConfig(); |
| 445 | return config[key]; | |
1c32fe84Patricio Beltran9 years ago | 446 | } |
| 447 | | |
| 448 | /** | |
94cd5149Artem Egorov8 years ago | 449 | * Returns the specified setting from exp.json if it exists |
1c32fe84Patricio Beltran9 years ago | 450 | */ |
0d77292aJiglioNero4 years ago | 451 | private async readExpJson(): Promise<ExpConfig> { |
94cd5149Artem Egorov8 years ago | 452 | const expJsonPath = this.pathToFileInWorkspace(EXP_JSON); |
dc94981bQuan Jin3 years ago | 453 | return this.fs.readFile(expJsonPath).then(content => { |
| 454 | return stripJsonTrailingComma(content.toString()); | |
| 455 | }); | |
1c32fe84Patricio Beltran9 years ago | 456 | } |
| 457 | | |
0d77292aJiglioNero4 years ago | 458 | private async readAppJson(): Promise<AppJson> { |
94cd5149Artem Egorov8 years ago | 459 | const appJsonPath = this.pathToFileInWorkspace(APP_JSON); |
7888898eEzio Li3 years ago | 460 | logger.log(`Getting app.json path: ${appJsonPath}`); |
dc94981bQuan Jin3 years ago | 461 | return this.fs.readFile(appJsonPath).then(content => { |
| 462 | return stripJsonTrailingComma(content.toString()); | |
| 463 | }); | |
1c32fe84Patricio Beltran9 years ago | 464 | } |
| 465 | | |
0d77292aJiglioNero4 years ago | 466 | private async writeAppJson(config: AppJson): Promise<AppJson> { |
94cd5149Artem Egorov8 years ago | 467 | const appJsonPath = this.pathToFileInWorkspace(APP_JSON); |
0d77292aJiglioNero4 years ago | 468 | await this.fs.writeFile(appJsonPath, JSON.stringify(config, null, 2)); |
| 469 | return config; | |
1c32fe84Patricio Beltran9 years ago | 470 | } |
| 471 | | |
ce5e88eeYuri Skorokhodov5 years ago | 472 | private getAppPackageInformation(): Promise<IPackageInformation> { |
66412fdfRuslan Bikkinin7 years ago | 473 | return new Package(this.projectRootPath, { fileSystem: this.fs }).parsePackageInformation(); |
| 474 | } | |
| 475 | | |
1c32fe84Patricio Beltran9 years ago | 476 | /** |
38edb09eAlexander Sorokin9 years ago | 477 | * Path to a given file from the workspace root |
1c32fe84Patricio Beltran9 years ago | 478 | */ |
| 479 | private pathToFileInWorkspace(filename: string): string { | |
94cd5149Artem Egorov8 years ago | 480 | return path.join(this.projectRootPath, filename).replace(DBL_SLASHES, "/"); |
1c32fe84Patricio Beltran9 years ago | 481 | } |
| 482 | | |
a57e740bPatricio Beltran9 years ago | 483 | /** |
dc94981bQuan Jin3 years ago | 484 | * Works as a constructor but only initializes when it's actually needed. |
a57e740bPatricio Beltran9 years ago | 485 | */ |
716f31d0JiglioNero5 years ago | 486 | private async lazilyInitialize(): Promise<void> { |
a57e740bPatricio Beltran9 years ago | 487 | if (!this.hasInitialized) { |
| 488 | this.hasInitialized = true; | |
716f31d0JiglioNero5 years ago | 489 | await this.preloadExponentDependency(); |
09f6024fHeniker4 years ago | 490 | void XDL.configReactNativeVersionWarnings(); |
| 491 | void XDL.attachLoggerStream(this.projectRootPath, { | |
a57e740bPatricio Beltran9 years ago | 492 | stream: { |
| 493 | write: (chunk: any) => { | |
| 494 | if (chunk.level <= 30) { | |
0a68f8dbArtem Egorov8 years ago | 495 | this.logger.logStream(chunk.msg); |
3cf8f692Ezio Li3 years ago | 496 | this.logger.logStream("\n"); |
a57e740bPatricio Beltran9 years ago | 497 | } else if (chunk.level === 40) { |
0a68f8dbArtem Egorov8 years ago | 498 | this.logger.warning(chunk.msg); |
a57e740bPatricio Beltran9 years ago | 499 | } else { |
0a68f8dbArtem Egorov8 years ago | 500 | this.logger.error(chunk.msg); |
a57e740bPatricio Beltran9 years ago | 501 | } |
| 502 | }, | |
| 503 | }, | |
| 504 | type: "raw", | |
| 505 | }); | |
| 506 | } | |
| 507 | } | |
57fee98eEzio Li3 years ago | 508 | |
| 509 | public async getExpoEasProjectOwner(): Promise<string | null> { | |
| 510 | const appJsonPath = this.pathToFileInWorkspace(APP_JSON); | |
| 511 | try { | |
| 512 | return JSON.parse(fs.readFileSync(appJsonPath, "utf-8")).expo.owner == undefined | |
| 513 | ? null | |
| 514 | : JSON.parse(fs.readFileSync(appJsonPath, "utf-8")).expo.owner; | |
| 515 | } catch { | |
| 516 | return null; | |
| 517 | } | |
| 518 | } | |
| 519 | | |
| 520 | public async getExpoEasProjectId(): Promise<string | null> { | |
| 521 | const appJsonPath = this.pathToFileInWorkspace(APP_JSON); | |
| 522 | try { | |
| 523 | return JSON.parse(fs.readFileSync(appJsonPath, "utf-8")).expo.extra.eas.projectId == | |
| 524 | undefined | |
| 525 | ? null | |
| 526 | : JSON.parse(fs.readFileSync(appJsonPath, "utf-8")).expo.extra.eas.projectId; | |
| 527 | } catch { | |
| 528 | return null; | |
| 529 | } | |
| 530 | } | |
| 531 | | |
| 532 | public async getExpoEasProjectName(): Promise<string | null> { | |
| 533 | const appJsonPath = this.pathToFileInWorkspace(APP_JSON); | |
| 534 | try { | |
| 535 | return JSON.parse(fs.readFileSync(appJsonPath, "utf-8")).expo.name == undefined | |
| 536 | ? null | |
| 537 | : JSON.parse(fs.readFileSync(appJsonPath, "utf-8")).expo.name; | |
| 538 | } catch { | |
| 539 | return null; | |
| 540 | } | |
| 541 | } | |
5c8365a6Artem Egorov8 years ago | 542 | } |