├── .eslintignore ├── .eslintrc.js ├── .gh-assets ├── banner.png ├── banner.svg └── float-box.svg ├── .github ├── .COMMIT_TEMPLATE ├── ISSUE_TEMPLATE │ └── config.yml ├── PULL_REQUEST_TEMPLATE ├── lock.yml └── workflows │ ├── issue-stale.yml │ ├── issue-triage.yml │ ├── release-expo-cli-spec.yml │ ├── test.yml │ └── test_windows.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── README.md ├── assets └── fig │ ├── android.png │ ├── app-store.png │ ├── apple.png │ ├── block.png │ ├── clear.png │ ├── config.png │ ├── customize.png │ ├── dev.png │ ├── devices.png │ ├── diagnostics.png │ ├── doctor.png │ ├── download.png │ ├── eject.png │ ├── expo.png │ ├── export.png │ ├── false.png │ ├── force.png │ ├── help.png │ ├── info.png │ ├── init.png │ ├── install.png │ ├── key.png │ ├── lan.png │ ├── latest.png │ ├── list.png │ ├── localhost.png │ ├── lock.png │ ├── login.png │ ├── logout.png │ ├── metro.png │ ├── minify.png │ ├── number.png │ ├── offline.png │ ├── path.png │ ├── play-store.png │ ├── play.png │ ├── prebuild.png │ ├── publish-rollback.png │ ├── publish.png │ ├── push-clear.png │ ├── push.png │ ├── quiet.png │ ├── register.png │ ├── scheme.png │ ├── send.png │ ├── skip.png │ ├── string.png │ ├── true.png │ ├── tunnel.png │ ├── upgrade.png │ ├── url.png │ ├── verbose.png │ ├── web.png │ ├── webhook-add.png │ ├── webhook-remove.png │ ├── webhook-update.png │ ├── webhook.png │ ├── webpack.png │ └── wifi.png ├── codecov.yml ├── jest.config.js ├── jest ├── babel.config.js └── unit-test-config.js ├── lerna.json ├── package.json ├── packages ├── babel-preset-cli │ ├── LICENSE │ ├── README.md │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── index-test.js.snap │ │ └── index-test.js │ ├── index.js │ ├── jest.config.js │ └── package.json ├── config-plugins │ └── README.md ├── config-types │ └── README.md ├── config │ └── README.md ├── create-expo-app │ └── README.md ├── dev-server │ └── README.md ├── dev-tools │ ├── .eslintrc.js │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── TESTING.md │ ├── __mocks__ │ │ └── xdl.js │ ├── babel.config.js │ ├── common │ │ ├── constants.js │ │ ├── createApolloClient.js │ │ ├── sets.js │ │ ├── state.js │ │ ├── store.js │ │ ├── strings.js │ │ ├── svg.js │ │ └── validations.js │ ├── components │ │ ├── Boundary.js │ │ ├── ContentGroup.js │ │ ├── ContentGroupHeader.js │ │ ├── DeprecatedBanner.js │ │ ├── GlobalToasts.js │ │ ├── IndexPageErrors.js │ │ ├── InputWithButton.js │ │ ├── InputWithLabel.js │ │ ├── Loader.js │ │ ├── Log.js │ │ ├── LoggerIcon.js │ │ ├── NetworkGroupButton.js │ │ ├── NetworkGroupHeader.js │ │ ├── OptionsButton.js │ │ ├── PrimaryButtonWithStates.js │ │ ├── ProjectManager.js │ │ ├── ProjectManagerDeviceTab.js │ │ ├── ProjectManagerLayout.js │ │ ├── ProjectManagerPublishingSection.js │ │ ├── ProjectManagerSection.js │ │ ├── ProjectManagerSectionHeader.js │ │ ├── ProjectManagerSidebarOptions.js │ │ ├── ProjectManagerToolbar.js │ │ ├── QRCode.js │ │ ├── Root.js │ │ ├── SettingsControl.js │ │ ├── SmallButton.js │ │ ├── SmallSquareButton.js │ │ ├── StackTrace.js │ │ ├── TextareaWithLabel.js │ │ └── UserIndicator.js │ ├── fragmentTypes.json │ ├── higher-order │ │ └── withRedux.js │ ├── jest.config.js │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── pages │ │ ├── _document.js │ │ └── index.js │ ├── server │ │ ├── DevToolsServer.ts │ │ ├── asynciterators │ │ │ ├── eventEmitterToAsyncIterator.ts │ │ │ └── mergeAsyncIterators.ts │ │ ├── babel.config.js │ │ ├── dev-server.ts │ │ ├── extract-fragment-types.ts │ │ ├── graphql │ │ │ ├── AsyncIterableRingBuffer.ts │ │ │ ├── GraphQLSchema.ts │ │ │ ├── Issues.ts │ │ │ ├── __tests__ │ │ │ │ ├── GraphQLSchema-test.ts │ │ │ │ └── __snapshots__ │ │ │ │ │ └── GraphQLSchema-test.ts.snap │ │ │ └── createContext.ts │ │ └── index.ts │ ├── static │ │ ├── android-chrome-192x192.png │ │ ├── apple-touch-icon.png │ │ ├── default-project-icons │ │ │ ├── bordered-1.png │ │ │ ├── bordered-10.png │ │ │ ├── bordered-11.png │ │ │ ├── bordered-12.png │ │ │ ├── bordered-13.png │ │ │ ├── bordered-14.png │ │ │ ├── bordered-15.png │ │ │ ├── bordered-2.png │ │ │ ├── bordered-3.png │ │ │ ├── bordered-4.png │ │ │ ├── bordered-5.png │ │ │ ├── bordered-6.png │ │ │ ├── bordered-7.png │ │ │ ├── bordered-8.png │ │ │ └── bordered-9.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── fonts │ │ │ ├── MaisonNeue-Bold.woff │ │ │ ├── MaisonNeue-Bold.woff2 │ │ │ ├── MaisonNeue-Book.woff │ │ │ ├── MaisonNeue-Book.woff2 │ │ │ ├── MaisonNeue-BookItalic.woff │ │ │ ├── MaisonNeue-BookItalic.woff2 │ │ │ ├── MaisonNeue-Demi.woff │ │ │ ├── MaisonNeue-Demi.woff2 │ │ │ ├── MaisonNeue-Light.woff │ │ │ ├── MaisonNeue-Light.woff2 │ │ │ ├── MaisonNeueMono-Regular.woff │ │ │ └── MaisonNeueMono-Regular.woff2 │ │ ├── mstile-150x150.png │ │ ├── safari-pinned-tab.svg │ │ └── site.webmanifest │ └── tsconfig.json ├── electron-adapter │ └── README.md ├── expo-cli │ ├── .eslintrc.js │ ├── LICENSE │ ├── README.md │ ├── __mocks__ │ │ ├── @expo │ │ │ ├── image-utils.ts │ │ │ └── rudder-sdk-node.ts │ │ ├── fs.js │ │ ├── ora.js │ │ └── resolve-from.js │ ├── babel.config.js │ ├── bin │ │ └── expo.js │ ├── e2e │ │ ├── TestUtils.ts │ │ ├── __tests__ │ │ │ ├── eject-test.ts │ │ │ ├── export-test.ts │ │ │ ├── init-test.ts │ │ │ ├── install-test.ts │ │ │ └── start-test.ts │ │ └── fixtures │ │ │ └── basic │ │ │ ├── App.js │ │ │ ├── app.json │ │ │ ├── babel.config.js │ │ │ └── package.json │ ├── jest.config.js │ ├── jest │ │ └── setup.ts │ ├── package.json │ ├── scripts │ │ ├── introspect.ts │ │ └── preversion.js │ ├── src │ │ ├── CommandError.ts │ │ ├── __tests__ │ │ │ ├── exp-test.ts │ │ │ ├── mock-utils.ts │ │ │ ├── project-utils.ts │ │ │ └── user-fixtures.ts │ │ ├── analytics │ │ │ ├── StatusEventEmitter.ts │ │ │ └── getDevClientProperties.ts │ │ ├── appleApi │ │ │ ├── authenticate.ts │ │ │ ├── bundleId.ts │ │ │ ├── contractMessages.ts │ │ │ ├── convertHTMLToASCII.ts │ │ │ ├── createManagers.ts │ │ │ ├── distributionCert.ts │ │ │ ├── ensureAppExists.ts │ │ │ ├── index.ts │ │ │ ├── keychain.ts │ │ │ ├── p12Certificate.ts │ │ │ ├── provisioningProfile.ts │ │ │ ├── provisioningProfileAdhoc.ts │ │ │ ├── pushKey.ts │ │ │ └── resolveCredentials.ts │ │ ├── commands │ │ │ ├── __tests__ │ │ │ │ └── webhooks-test.ts │ │ │ ├── auth │ │ │ │ ├── __tests__ │ │ │ │ │ └── accounts-test.ts │ │ │ │ ├── accounts.ts │ │ │ │ ├── login.ts │ │ │ │ ├── loginAsync.ts │ │ │ │ ├── logout.ts │ │ │ │ ├── logoutAsync.ts │ │ │ │ ├── register.ts │ │ │ │ ├── registerAsync.ts │ │ │ │ ├── whoami.ts │ │ │ │ └── whoamiAsync.ts │ │ │ ├── build │ │ │ │ ├── AndroidBuilder.ts │ │ │ │ ├── BaseBuilder.ts │ │ │ │ ├── BaseBuilder.types.ts │ │ │ │ ├── BuildError.ts │ │ │ │ ├── __tests__ │ │ │ │ │ └── build-ios-test.ts │ │ │ │ ├── buildAndroidAsync.ts │ │ │ │ ├── buildIosAsync.ts │ │ │ │ ├── buildStatusAsync.ts │ │ │ │ ├── buildWebAsync.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── findReusableBuildAsync.ts │ │ │ │ ├── getBuildStatusAsync.ts │ │ │ │ ├── getLatestReleaseAsync.ts │ │ │ │ ├── index.ts │ │ │ │ ├── ios │ │ │ │ │ ├── IOSBuilder.ts │ │ │ │ │ └── utils │ │ │ │ │ │ └── image.ts │ │ │ │ ├── logBuildMigration.ts │ │ │ │ ├── startBuildAsync.ts │ │ │ │ └── utils.ts │ │ │ ├── client │ │ │ │ ├── clientInstallAndroidAsync.ts │ │ │ │ ├── clientInstallIosAsync.ts │ │ │ │ ├── clientIosAsync.ts │ │ │ │ └── index.ts │ │ │ ├── credentials.ts │ │ │ ├── credentialsManagerAsync.ts │ │ │ ├── eject │ │ │ │ ├── Github.ts │ │ │ │ ├── __tests__ │ │ │ │ │ ├── createNativeProjectsFromTemplateAsync-test.ts │ │ │ │ │ ├── ensureConfigAsync-test.ts │ │ │ │ │ ├── platformOptions-test.ts │ │ │ │ │ ├── setupWarnings-test.ts │ │ │ │ │ └── updatePackageJson-test.ts │ │ │ │ ├── clearNativeFolder.ts │ │ │ │ ├── configureProjectAsync.ts │ │ │ │ ├── createNativeProjectsFromTemplateAsync.ts │ │ │ │ ├── customize.ts │ │ │ │ ├── customizeAsync.ts │ │ │ │ ├── eject.ts │ │ │ │ ├── ejectAsync.ts │ │ │ │ ├── ensureConfigAsync.ts │ │ │ │ ├── installNodeDependenciesAsync.ts │ │ │ │ ├── logNextSteps.ts │ │ │ │ ├── platformOptions.ts │ │ │ │ ├── prebuild.ts │ │ │ │ ├── prebuildAppAsync.ts │ │ │ │ ├── prebuildAsync.ts │ │ │ │ ├── resolveTemplate.ts │ │ │ │ ├── setupWarnings.ts │ │ │ │ ├── updatePackageJson.ts │ │ │ │ └── writeMetroConfig.ts │ │ │ ├── expokit │ │ │ │ ├── bundle-assets.ts │ │ │ │ ├── bundleAssetsAsync.ts │ │ │ │ ├── prepare-detached-build.ts │ │ │ │ └── prepareDetachedBuildAsync.ts │ │ │ ├── export │ │ │ │ ├── __tests__ │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── writeContents-test.ts.snap │ │ │ │ │ ├── createMetadataJson-test.ts │ │ │ │ │ ├── export-test.ts │ │ │ │ │ ├── exportAppAsync-test.ts │ │ │ │ │ └── writeContents-test.ts │ │ │ │ ├── createMetadataJson.ts │ │ │ │ ├── export.ts │ │ │ │ ├── exportAppAsync.ts │ │ │ │ ├── exportAsync.ts │ │ │ │ ├── mergeAppDistributions.ts │ │ │ │ └── writeContents.ts │ │ │ ├── fetch │ │ │ │ ├── fetchAndroidHashesAsync.ts │ │ │ │ ├── fetchAndroidKeystoreAsync.ts │ │ │ │ ├── fetchAndroidUploadCertAsync.ts │ │ │ │ ├── fetchIosCertsAsync.ts │ │ │ │ ├── index.ts │ │ │ │ └── utils.ts │ │ │ ├── index.ts │ │ │ ├── info │ │ │ │ ├── __tests__ │ │ │ │ │ └── upgrade-test.ts │ │ │ │ ├── config │ │ │ │ │ ├── config.ts │ │ │ │ │ └── configAsync.ts │ │ │ │ ├── doctor │ │ │ │ │ ├── depedencies │ │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ │ ├── explain.test.ts │ │ │ │ │ │ │ └── fixtures │ │ │ │ │ │ │ │ └── invalid-plugins.json │ │ │ │ │ │ ├── explain.ts │ │ │ │ │ │ └── explain.types.ts │ │ │ │ │ ├── doctorAsync.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── windows.ts │ │ │ │ ├── upgrade.ts │ │ │ │ └── upgradeAsync.ts │ │ │ ├── init.ts │ │ │ ├── initAsync.ts │ │ │ ├── install.ts │ │ │ ├── installAsync.ts │ │ │ ├── publish │ │ │ │ ├── __tests__ │ │ │ │ │ ├── publish-info-test.ts │ │ │ │ │ ├── publish-modify-test.ts │ │ │ │ │ └── publish-test.ts │ │ │ │ ├── deprecationNotice.ts │ │ │ │ ├── getUsageAsync.ts │ │ │ │ ├── publish.ts │ │ │ │ ├── publishAsync.ts │ │ │ │ ├── publishDetailsAsync.ts │ │ │ │ ├── publishHistoryAsync.ts │ │ │ │ ├── publishRollbackAsync.ts │ │ │ │ └── publishSetAsync.ts │ │ │ ├── push.ts │ │ │ ├── push │ │ │ │ ├── pushAndroidClearAsync.ts │ │ │ │ ├── pushAndroidShowAsync.ts │ │ │ │ └── pushAndroidUploadAsync.ts │ │ │ ├── run │ │ │ │ ├── android │ │ │ │ │ ├── resolveDeviceAsync.ts │ │ │ │ │ ├── runAndroid.ts │ │ │ │ │ └── spawnGradleAsync.ts │ │ │ │ ├── index.ts │ │ │ │ ├── ios │ │ │ │ │ ├── IOSDeploy.ts │ │ │ │ │ ├── Podfile.ts │ │ │ │ │ ├── XcodeBuild.ts │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ ├── developmentCodeSigning-test.ts │ │ │ │ │ │ └── fixtures │ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ │ └── signed-project.pbxproj │ │ │ │ │ ├── developmentCodeSigning.ts │ │ │ │ │ ├── installOnDeviceAsync.ts │ │ │ │ │ ├── resolveDeviceAsync.ts │ │ │ │ │ ├── resolveOptionsAsync.ts │ │ │ │ │ ├── runIos.ts │ │ │ │ │ └── startBundlerAsync.ts │ │ │ │ └── utils │ │ │ │ │ ├── Security.ts │ │ │ │ │ ├── __tests__ │ │ │ │ │ └── Security-test.ts │ │ │ │ │ ├── binaryPlist.ts │ │ │ │ │ ├── resolvePortAsync.ts │ │ │ │ │ └── schemes.ts │ │ │ ├── send.ts │ │ │ ├── sendAsync.ts │ │ │ ├── start.ts │ │ │ ├── start │ │ │ │ ├── TerminalUI.ts │ │ │ │ ├── __tests__ │ │ │ │ │ └── parseStartOptions-test.ts │ │ │ │ ├── installExitHooks.ts │ │ │ │ ├── parseStartOptions.ts │ │ │ │ └── startAsync.ts │ │ │ ├── upload.ts │ │ │ ├── url.ts │ │ │ ├── url │ │ │ │ ├── logArtifactUrl.ts │ │ │ │ ├── printRunInstructionsAsync.ts │ │ │ │ ├── urlApkAsync.ts │ │ │ │ ├── urlAsync.ts │ │ │ │ └── urlIpaAsync.ts │ │ │ ├── utils │ │ │ │ ├── ClientUpgradeUtils.ts │ │ │ │ ├── CreateApp.ts │ │ │ │ ├── GitIgnore.ts │ │ │ │ ├── ProjectUtils.ts │ │ │ │ ├── PublishUtils.ts │ │ │ │ ├── Tar.ts │ │ │ │ ├── TerminalLink.ts │ │ │ │ ├── __tests__ │ │ │ │ │ ├── CreateApp-test.ts │ │ │ │ │ ├── GitIgnore-test.ts │ │ │ │ │ ├── ProjectUtils-test.ts │ │ │ │ │ ├── autoAddConfigPluginsAsync-test.ts │ │ │ │ │ ├── bundledNativeModules-test.ts │ │ │ │ │ ├── isModuleSymlinked-test.ts │ │ │ │ │ ├── modifyConfigAsync-test.ts │ │ │ │ │ ├── npm-test.ts │ │ │ │ │ ├── url-test.ts │ │ │ │ │ ├── validateApplicationId-test.ts │ │ │ │ │ └── validateDependenciesVersions-test.ts │ │ │ │ ├── applyAsyncAction.ts │ │ │ │ ├── autoAddConfigPluginsAsync.ts │ │ │ │ ├── bundledNativeModules.ts │ │ │ │ ├── cli-table.ts │ │ │ │ ├── createFileTransform.ts │ │ │ │ ├── deprecatedExtensionWarnings.ts │ │ │ │ ├── environment.ts │ │ │ │ ├── extractTemplateAppAsync.ts │ │ │ │ ├── fetch-cache │ │ │ │ │ ├── FileSystemCache.ts │ │ │ │ │ ├── fetch.ts │ │ │ │ │ └── response.ts │ │ │ │ ├── getOrPromptApplicationId.ts │ │ │ │ ├── glob.ts │ │ │ │ ├── isModuleSymlinked.ts │ │ │ │ ├── logConfigWarnings.ts │ │ │ │ ├── maybeBailOnGitStatusAsync.ts │ │ │ │ ├── modifyConfigAsync.ts │ │ │ │ ├── npm.ts │ │ │ │ ├── openInEditorAsync.ts │ │ │ │ ├── profileMethod.ts │ │ │ │ ├── progress.ts │ │ │ │ ├── promise.ts │ │ │ │ ├── sendTo.ts │ │ │ │ ├── truncateLastLinesAsync.ts │ │ │ │ ├── typescript │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ ├── ensureTypeScriptSetup-test.ts │ │ │ │ │ │ └── updateTSConfig-test.ts │ │ │ │ │ ├── ensureTypeScriptSetup.ts │ │ │ │ │ ├── resolveModules.ts │ │ │ │ │ └── updateTSConfig.ts │ │ │ │ ├── url.ts │ │ │ │ ├── urlOpts.ts │ │ │ │ ├── validateApplicationId.ts │ │ │ │ ├── validateDependenciesVersions.ts │ │ │ │ └── web │ │ │ │ │ ├── __tests__ │ │ │ │ │ ├── ensureWebSetup-test.ts │ │ │ │ │ └── getMissingPackages-test.ts │ │ │ │ │ ├── ensureWebSetup.ts │ │ │ │ │ └── getMissingPackages.ts │ │ │ ├── webhooks.ts │ │ │ └── webhooks │ │ │ │ ├── __tests__ │ │ │ │ └── webhooksUpdateAsync-test.ts │ │ │ │ ├── utils.ts │ │ │ │ ├── webhooksAddAsync.ts │ │ │ │ ├── webhooksAsync.ts │ │ │ │ ├── webhooksRemoveAsync.ts │ │ │ │ └── webhooksUpdateAsync.ts │ │ ├── credentials │ │ │ ├── __tests__ │ │ │ │ ├── api-android-basic-test.ts │ │ │ │ ├── api-ios-basic-test.ts │ │ │ │ └── fixtures │ │ │ │ │ ├── mock-base64-data.ts │ │ │ │ │ ├── mocks-android.ts │ │ │ │ │ ├── mocks-constants.ts │ │ │ │ │ ├── mocks-context.ts │ │ │ │ │ └── mocks-ios.ts │ │ │ ├── actions │ │ │ │ ├── list.ts │ │ │ │ └── promptForCredentials.ts │ │ │ ├── api │ │ │ │ ├── AndroidApi.ts │ │ │ │ ├── AndroidApiV2Wrapper.ts │ │ │ │ ├── IosApi.ts │ │ │ │ └── IosApiV2Wrapper.ts │ │ │ ├── context.ts │ │ │ ├── credentials.ts │ │ │ ├── credentialsJson │ │ │ │ ├── __tests__ │ │ │ │ │ ├── read-test.ts │ │ │ │ │ └── update-test.ts │ │ │ │ ├── read.ts │ │ │ │ └── update.ts │ │ │ ├── index.ts │ │ │ ├── route.ts │ │ │ ├── utils │ │ │ │ ├── __mocks__ │ │ │ │ │ └── git.ts │ │ │ │ ├── __tests__ │ │ │ │ │ └── provisioningProfile-test.ts │ │ │ │ ├── git.ts │ │ │ │ ├── provisioningProfile.ts │ │ │ │ └── validateKeystore.ts │ │ │ └── views │ │ │ │ ├── AndroidCredentials.ts │ │ │ │ ├── AndroidKeystore.ts │ │ │ │ ├── AndroidPushCredentials.ts │ │ │ │ ├── IosAppCredentials.ts │ │ │ │ ├── IosDistCert.ts │ │ │ │ ├── IosProvisioningProfile.ts │ │ │ │ ├── IosProvisioningProfileAdhoc.ts │ │ │ │ ├── IosPushCredentials.ts │ │ │ │ ├── Select.ts │ │ │ │ ├── SetupAndroidKeystore.ts │ │ │ │ ├── SetupIosBuildCredentials.ts │ │ │ │ ├── SetupIosDist.ts │ │ │ │ ├── SetupIosProvisioningProfile.ts │ │ │ │ ├── SetupIosPush.ts │ │ │ │ └── __tests__ │ │ │ │ ├── AndroidKeystore-Remove-test.ts │ │ │ │ ├── AndroidKeystore-Update-test.ts │ │ │ │ ├── IosDistCert-test.ts │ │ │ │ ├── IosProvisioningProfile-test.ts │ │ │ │ ├── IosPushCredentials-test.ts │ │ │ │ ├── SetupAndroidBuildCredentialsFromLocal-test.ts │ │ │ │ ├── SetupAndroidKeystore-test.ts │ │ │ │ ├── SetupIosBuildCredentialsFromLocal-test.ts │ │ │ │ ├── SetupIosDist-test.ts │ │ │ │ ├── SetupIosPush-test.ts │ │ │ │ └── SetupProvisioningProfile-test.ts │ │ ├── exp.ts │ │ ├── log.ts │ │ └── utils │ │ │ ├── __mocks__ │ │ │ └── prompts.ts │ │ │ ├── __tests__ │ │ │ ├── getRemoteVersionsForSdk.test.ts │ │ │ └── matchFileNameOrURLFromStackTrace-test.ts │ │ │ ├── formatStackTrace.ts │ │ │ ├── getRemoteVersionsForSdk.ts │ │ │ ├── handleErrors.ts │ │ │ ├── matchFileNameOrURLFromStackTrace.ts │ │ │ ├── migration.ts │ │ │ ├── ora.ts │ │ │ ├── prompts.ts │ │ │ ├── update.ts │ │ │ └── validators.ts │ └── tsconfig.json ├── expo-codemod │ ├── .eslintrc.js │ ├── LICENSE │ ├── README.md │ ├── bin │ │ └── expo-codemod.js │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── cli.ts │ │ └── transforms │ │ │ ├── __testfixtures__ │ │ │ └── sdk33-imports │ │ │ │ ├── namespace-as-expo.input.js │ │ │ │ └── namespace-as-expo.output.js │ │ │ ├── __tests__ │ │ │ ├── sdk33-imports-test.ts │ │ │ ├── sdk37-imports-test.ts │ │ │ └── sdk41-async-storage-test.ts │ │ │ ├── sdk33-imports.ts │ │ │ ├── sdk37-imports.ts │ │ │ └── sdk41-async-storage.ts │ └── tsconfig.json ├── expo-doctor │ └── README.md ├── expo-env-info │ └── README.md ├── expo-optimize │ └── README.md ├── image-utils │ └── README.md ├── install-expo-modules │ └── README.md ├── json-file │ └── README.md ├── metro-config │ └── README.md ├── next-adapter │ └── README.md ├── osascript │ └── README.md ├── package-manager │ └── README.md ├── pkcs12 │ └── README.md ├── plist │ └── README.md ├── pod-install │ └── README.md ├── prebuild-config │ └── README.md ├── pwa │ └── README.md ├── schemer │ └── README.md ├── traveling-fastlane │ └── README.md ├── uri-scheme │ └── README.md ├── webpack-config │ └── README.md └── xdl │ ├── LICENSE │ ├── LICENSE-third-party │ ├── README.md │ ├── __mocks__ │ ├── @expo │ │ ├── image-utils.ts │ │ └── rudder-sdk-node.ts │ ├── analytics-node.ts │ ├── dtrace-provider.ts │ ├── fs.ts │ ├── os.ts │ └── resolve-from.ts │ ├── babel.config.js │ ├── binaries │ ├── linux │ │ ├── adb │ │ │ └── adb │ │ └── get-path-bash │ ├── osx │ │ ├── adb │ │ │ └── adb │ │ ├── get-path-bash │ │ └── watchman │ │ │ └── watchman │ └── windows │ │ └── adb │ │ ├── AdbWinApi.dll │ │ ├── AdbWinUsbApi.dll │ │ └── adb.exe │ ├── caches │ ├── schema-10.0.0.json │ ├── schema-11.0.0.json │ ├── schema-12.0.0.json │ ├── schema-13.0.0.json │ ├── schema-14.0.0.json │ ├── schema-15.0.0.json │ ├── schema-16.0.0.json │ ├── schema-17.0.0.json │ ├── schema-18.0.0.json │ ├── schema-19.0.0.json │ ├── schema-20.0.0.json │ ├── schema-21.0.0.json │ ├── schema-22.0.0.json │ ├── schema-23.0.0.json │ ├── schema-24.0.0.json │ ├── schema-25.0.0.json │ ├── schema-26.0.0.json │ ├── schema-27.0.0.json │ ├── schema-28.0.0.json │ ├── schema-29.0.0.json │ ├── schema-30.0.0.json │ ├── schema-31.0.0.json │ ├── schema-32.0.0.json │ ├── schema-33.0.0.json │ ├── schema-34.0.0.json │ ├── schema-35.0.0.json │ ├── schema-36.0.0.json │ ├── schema-37.0.0.json │ ├── schema-38.0.0.json │ ├── schema-39.0.0.json │ ├── schema-40.0.0.json │ ├── schema-41.0.0.json │ ├── schema-42.0.0.json │ ├── schema-43.0.0.json │ ├── schema-44.0.0.json │ ├── schema-45.0.0.json │ ├── schema-46.0.0.json │ ├── schema-47.0.0.json │ ├── schema-48.0.0.json │ ├── schema-49.0.0.json │ ├── schema-7.0.0.json │ ├── schema-8.0.0.json │ ├── schema-9.0.0.json │ └── versions.json │ ├── jest.config.js │ ├── jest │ ├── fs-mock-setup.ts │ └── integration-test-config.js │ ├── package.json │ ├── scripts │ └── updateCaches.js │ ├── src │ ├── Analytics.ts │ ├── Android.ts │ ├── ApiV2.ts │ ├── Binaries.ts │ ├── BundleIdentifier.ts │ ├── Config.ts │ ├── ConnectionStatus.ts │ ├── DevSession.ts │ ├── EmbeddedAssets.ts │ ├── Env.ts │ ├── ErrorCode.ts │ ├── Exp.ts │ ├── Extract.ts │ ├── LoadingEvent.ts │ ├── Logger.ts │ ├── Project.ts │ ├── ProjectAssets.ts │ ├── ProjectSettings.ts │ ├── Prompts.ts │ ├── Session.ts │ ├── SimControl.ts │ ├── SimControlLogs.ts │ ├── Simulator.ts │ ├── ThirdParty.ts │ ├── UnifiedAnalytics.ts │ ├── UrlUtils.ts │ ├── User.ts │ ├── UserSettings.ts │ ├── Versions.ts │ ├── Watchman.ts │ ├── Webpack.ts │ ├── XDLError.ts │ ├── Xcode.ts │ ├── __integration_tests__ │ │ ├── Simulator-test.ts │ │ ├── UserManager-test.ts │ │ └── UserSessions-test.ts │ ├── __tests__ │ │ ├── Env-test.ts │ │ ├── Project-publishAsync-test.ts │ │ ├── ProjectSettings-test.ts │ │ ├── SimControl-test.ts │ │ ├── SimControlLogs-test.ts │ │ ├── UrlUtils-test.ts │ │ ├── User-test.ts │ │ ├── UserSettings-test.ts │ │ ├── detach │ │ │ ├── PKCS12Utils-test.ts │ │ │ └── __snapshots__ │ │ │ │ └── PKCS12Utils-test.ts.snap │ │ ├── fixtures │ │ │ └── publish-test-app │ │ │ │ ├── App.js │ │ │ │ ├── app.json │ │ │ │ └── package.json │ │ └── tools │ │ │ └── FsCache-test.js │ ├── apple │ │ ├── AppleDevice.ts │ │ ├── CoreSimulator.ts │ │ ├── native-run │ │ │ ├── ios │ │ │ │ ├── lib │ │ │ │ │ ├── client │ │ │ │ │ │ ├── afc.ts │ │ │ │ │ │ ├── client.ts │ │ │ │ │ │ ├── debugserver.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── installation_proxy.ts │ │ │ │ │ │ ├── lockdownd.ts │ │ │ │ │ │ ├── mobile_image_mounter.ts │ │ │ │ │ │ └── usbmuxd.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── lib-errors.ts │ │ │ │ │ ├── manager.ts │ │ │ │ │ └── protocol │ │ │ │ │ │ ├── afc.ts │ │ │ │ │ │ ├── gdb.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── lockdown.ts │ │ │ │ │ │ ├── protocol.ts │ │ │ │ │ │ └── usbmux.ts │ │ │ │ └── utils │ │ │ │ │ └── xcode.ts │ │ │ └── utils │ │ │ │ └── process.ts │ │ └── utils │ │ │ ├── ensureSimulatorAppRunningAsync.ts │ │ │ └── waitForActionAsync.ts │ ├── credentials │ │ └── AndroidCredentials.ts │ ├── detach │ │ ├── AssetBundle.ts │ │ ├── Detach.ts │ │ ├── ExponentTools.ts │ │ ├── IosCodeSigning.ts │ │ ├── IosPlist.ts │ │ ├── IosWorkspace.ts │ │ ├── Logger.ts │ │ ├── PKCS12Utils.ts │ │ ├── StandaloneBuildFlags.ts │ │ └── StandaloneContext.ts │ ├── gating │ │ ├── FeatureGateEnvOverrides.ts │ │ ├── FeatureGateKey.ts │ │ ├── FeatureGateTestOverrides.ts │ │ ├── FeatureGating.ts │ │ ├── __mocks__ │ │ │ └── FeatureGateTestOverrides.ts │ │ └── __tests__ │ │ │ └── FeatureGate-test.ts │ ├── index.ts │ ├── internal.ts │ ├── ip.ts │ ├── logs │ │ ├── PackagerLogsStream.ts │ │ ├── TableText.ts │ │ ├── TerminalLink.ts │ │ └── __tests__ │ │ │ ├── PackagerLogsStream-test.ts │ │ │ └── __snapshots__ │ │ │ └── PackagerLogsStream-test.ts.snap │ ├── project │ │ ├── Doctor.ts │ │ ├── ExpSchema.ts │ │ ├── ProjectUtils.ts │ │ ├── __tests__ │ │ │ ├── ExpSchema-test.ts │ │ │ ├── createBundlesAsync-test.ts │ │ │ └── getPublishExpConfigAsync-test.ts │ │ ├── createBundlesAsync.ts │ │ ├── errors.ts │ │ ├── getPublishExpConfigAsync.ts │ │ ├── publishAsync.ts │ │ └── runHook.ts │ ├── reporter │ │ └── index.ts │ ├── start │ │ ├── ExpoUpdatesManifestHandler.ts │ │ ├── LoadingPageHandler.ts │ │ ├── ManifestHandler.ts │ │ ├── __tests__ │ │ │ ├── ExpoUpdatesManifestHandler-test.ts │ │ │ ├── ManifestHandler-test.ts │ │ │ ├── __snapshots__ │ │ │ │ └── ManifestHandler-test.ts.snap │ │ │ └── fixtures │ │ │ │ └── icon.png │ │ ├── getFreePortAsync.ts │ │ ├── ngrok.ts │ │ ├── ngrokUrl.ts │ │ ├── resolveNgrok.ts │ │ ├── startAsync.ts │ │ ├── startDevServerAsync.ts │ │ ├── startLegacyExpoServerAsync.ts │ │ ├── startLegacyReactNativeServerAsync.ts │ │ └── watchBabelConfig.ts │ ├── tools │ │ ├── ArtifactUtils.ts │ │ ├── FsCache.ts │ │ ├── ImageUtils.ts │ │ ├── ModuleVersion.ts │ │ ├── __tests__ │ │ │ └── resolveEntryPoint-test.ts │ │ └── resolveEntryPoint.ts │ ├── utils │ │ ├── Semaphore.ts │ │ ├── choosePortAsync.ts │ │ ├── delayAsync.ts │ │ ├── downloadApkAsync.ts │ │ ├── downloadAppAsync.ts │ │ ├── getRunningProcess.ts │ │ ├── isDevClientPackageInstalled.ts │ │ ├── parseBinaryPlistAsync.ts │ │ └── profileMethod.ts │ └── webpack-utils │ │ ├── WebpackEnvironment.ts │ │ └── formatWebpackMessages.ts │ ├── static │ └── loading-page │ │ └── index.html │ └── tsconfig.json ├── scripts ├── build-packages-toc.js ├── changelog-draft.js └── publish.js ├── ts-declarations ├── better-opn │ └── index.d.ts ├── decache │ └── index.d.ts ├── exec-async │ └── index.d.ts ├── expo__bunyan │ └── index.d.ts ├── expo__simple-spinner │ └── index.d.ts ├── freeport-async │ └── index.d.ts ├── hasbin │ └── index.d.ts ├── json-schema-deref-sync │ └── index.d.ts ├── json-schema-traverse │ └── index.d.ts ├── md5hex │ └── index.d.ts ├── metro-babel-transformer │ └── index.d.ts ├── metro-config │ └── index.d.ts ├── metro-source-map │ └── index.d.ts ├── metro │ └── index.d.ts ├── mini-css-extract-plugin │ └── index.d.ts ├── pacote │ └── index.d.ts ├── pnp-webpack-plugin │ └── index.d.ts ├── postcss-safe-parser │ └── index.d.ts ├── probe-image-size │ └── index.d.ts ├── qrcode-terminal │ └── index.d.ts ├── react-dev-utils │ └── index.d.ts ├── read-last-lines │ └── index.d.ts ├── slugid │ └── index.d.ts ├── update-check │ └── index.d.ts ├── webpack-deep-scope-plugin │ └── index.d.ts └── xcode │ └── index.d.ts ├── tsconfig.base.json ├── unlinked-packages ├── README.md └── configure-splash-screen │ ├── .eslintrc.js │ ├── README.md │ ├── __mocks__ │ ├── fs.ts │ └── xcode.ts │ ├── jest.config.js │ ├── package.json │ ├── src │ ├── SplashScreenConfig.ts │ ├── __tests__ │ │ ├── cli-command-test.ts │ │ ├── fixtures │ │ │ ├── background.png │ │ │ └── background_dark.png │ │ └── helpers.ts │ ├── android │ │ ├── AndroidManifest.xml.ts │ │ ├── Colors.xml.ts │ │ ├── Drawable.xml.ts │ │ ├── Drawables.ts │ │ ├── Paths.ts │ │ ├── Strings.xml.ts │ │ ├── Styles.xml.ts │ │ ├── __tests__ │ │ │ ├── AndroidManifest.xml-test.ts │ │ │ ├── Colors.xml-test.ts │ │ │ ├── Drawable.xml-test.ts │ │ │ ├── Drawables-test.ts │ │ │ ├── Strings.xml-test.ts │ │ │ ├── Styles.xml-test.ts │ │ │ ├── fixtures │ │ │ │ ├── react-native-project-structure-with-splash-screen-configured.ts │ │ │ │ └── react-native-project-structure.ts │ │ │ └── index-test.ts │ │ └── index.ts │ ├── cli-command.ts │ ├── constants.ts │ ├── index-cli.ts │ ├── index.ts │ ├── ios │ │ ├── BackgroundAsset.ts │ │ ├── Contents.json.ts │ │ ├── ImageAsset.ts │ │ ├── Info.plist.ts │ │ ├── Paths.ts │ │ ├── Storyboard.ts │ │ ├── __tests__ │ │ │ ├── BackgroundAsset-test.ts │ │ │ ├── ImageAsset-test.ts │ │ │ ├── Info.plist-test.ts │ │ │ ├── Storyboard-test.ts │ │ │ ├── __snapshots__ │ │ │ │ └── index-test.ts.snap │ │ │ ├── fixtures │ │ │ │ ├── react-native-init-template-project.pbxproj │ │ │ │ ├── react-native-project-structure-with-splash-screen-configured.ts │ │ │ │ └── react-native-project-structure.ts │ │ │ └── index-test.ts │ │ ├── index.ts │ │ └── pbxproj.ts │ ├── utils │ │ ├── StateManager.ts │ │ ├── file-utils.ts │ │ └── string-utils.ts │ ├── validators │ │ ├── FromJsonValidator.ts │ │ ├── __tests__ │ │ │ ├── android-test.ts │ │ │ └── ios-test.ts │ │ ├── index.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── xcode │ │ └── index.ts │ └── xml-manipulation │ │ └── index.ts │ ├── tsconfig.json │ └── yarn.lock └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | # Ignore all node_modules and vendored Ruby 2 | node_modules 3 | vendor 4 | **/ts-declarations/* 5 | 6 | # Ignore build artifacts 7 | /packages/*/build 8 | /packages/osascript/lib 9 | /packages/webpack-config/webpack 10 | /packages/config/src/__tests__/fixtures/behavior/syntax-error 11 | /unlinked-packages/*/build 12 | 13 | # Ignore test fixtures 14 | /packages/install-expo-modules/**/fixtures 15 | 16 | # Generated 17 | .expo 18 | .history 19 | .next 20 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['universe/node'], 4 | overrides: [ 5 | { 6 | files: ['**/__tests__/*'], 7 | }, 8 | ], 9 | globals: { 10 | jasmine: false, 11 | }, 12 | rules: { 13 | 'no-constant-condition': ['warn', { checkLoops: false }], 14 | 'sort-imports': [ 15 | 'warn', 16 | { 17 | ignoreCase: true, 18 | ignoreDeclarationSort: true, 19 | }, 20 | ], 21 | 'flowtype/no-types-missing-file-annotation': 'off', 22 | }, 23 | settings: { 24 | react: { 25 | version: 'detect', 26 | }, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /.gh-assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/.gh-assets/banner.png -------------------------------------------------------------------------------- /.github/.COMMIT_TEMPLATE: -------------------------------------------------------------------------------- 1 | # 🚨🚨ATTENTION🚨🚨 2 | # Please follow the commit message format below 📝 3 | # 4 | # : 5 | # [optional body] 6 | # [optional footer] 7 | # 8 | # types: "fix" = patches a bug in the codebase 9 | # "feat" = introduces new feature to codebase 10 | # "improvement" = improvement without adding a feature or fixing a bug 11 | # various others: "chore" "docs" "style" "refactor" "perf" "test" 12 | # 13 | # Include "BREAKING CHANGE:" at the beginning of the body or footer if your commit introduces a breaking API change 14 | 15 | 16 | 17 | 18 | 19 | 20 | # Example commit messages @ https://www.conventionalcommits.org/en/v1.0.0-beta.4/#examples 21 | # Thank you for cooperating, this allows us to auto-generate a fantastic changelog :) 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Issue with EAS CLI 4 | url: https://github.com/expo/eas-cli/issues/new/choose 5 | about: Report issues related to eas-cli in the expo/eas-cli repo. 6 | - name: Issue with Expo CLI or Expo Config Plugins 7 | url: https://github.com/expo/expo/issues/new?labels=needs+review%2CCLI&template=bug_report_cli.yml 8 | about: Report issues related to npx expo and the versioned CLI in the expo/expo repo. 9 | - name: Issue with Expo Webpack integrations 10 | url: https://github.com/expo/expo-webpack-integrations/issues/new?labels=needs+review%2CCLI&template=bug_report_cli.yml 11 | about: Report issues related to `@expo/electron-adapter`, `@expo/next-adapter`, `@expo/webpack-config`, and `expo-pwa` in the expo/expo-webpack-integrations repo. 12 | - name: Expo Developers Discord 13 | url: https://chat.expo.dev/ 14 | about: Join other developers in discussions regarding Expo. 15 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | # Why 2 | 3 | 13 | 14 | # How 15 | 16 | 19 | 20 | # Test Plan 21 | 22 | 25 | -------------------------------------------------------------------------------- /.github/lock.yml: -------------------------------------------------------------------------------- 1 | # Configuration for Lock Threads - https://github.com/dessant/lock-threads 2 | 3 | # Number of days of inactivity before a closed issue or pull request is locked 4 | # daysUntilLock: 60 5 | 6 | # Skip issues and pull requests created before a given timestamp. Timestamp must 7 | # follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable 8 | # skipCreatedBefore: false 9 | 10 | # Issues and pull requests with these labels will be ignored. Set to `[]` to disable 11 | # exemptLabels: [] 12 | 13 | # Label to add before locking, such as `outdated`. Set to `false` to disable 14 | # lockLabel: outdated 15 | 16 | # Comment to post before locking. Set to `false` to disable 17 | # lockComment: false 18 | 19 | # Assign `resolved` as the reason for locking. Set to `false` to disable 20 | # setLockReason: true 21 | 22 | # Limit to only `issues` or `pulls` 23 | # only: issues 24 | # Optionally, specify configuration settings just for `issues` or `pulls` 25 | # issues: 26 | # exemptLabels: 27 | # - help-wanted 28 | # lockLabel: outdated 29 | 30 | # pulls: 31 | # daysUntilLock: 30 32 | 33 | # Repository to extend settings from 34 | _extends: expo 35 | -------------------------------------------------------------------------------- /.github/workflows/issue-stale.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '0 * * * *' 6 | 7 | jobs: 8 | close-issues: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/stale@v4 12 | with: 13 | ascending: false 14 | operations-per-run: 300 15 | days-before-issue-stale: 90 16 | days-before-issue-close: 7 17 | stale-issue-label: 'stale' 18 | stale-issue-message: 'This issue is stale because it has been open for 60 days with no activity. If there is no activity in the next 7 days, the issue will be closed.' 19 | close-issue-message: 'This issue was closed because it has been inactive for 7 days since being marked as stale. Please open a new issue if you believe you are encountering a related problem.' 20 | days-before-pr-stale: -1 21 | days-before-pr-close: -1 22 | enable-statistics: true 23 | exempt-issue-labels: "needs review" 24 | exempt-all-assignees: true 25 | -------------------------------------------------------------------------------- /.github/workflows/release-expo-cli-spec.yml: -------------------------------------------------------------------------------- 1 | name: 'Update expo-cli spec' 2 | on: 3 | push: 4 | tags: 5 | - 'expo-cli@*' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | push-to-fig-autocomplete: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-node@v2 14 | - name: Get version 15 | id: expo-version 16 | env: 17 | FULL_VERSION: ${{ github.ref_name }} 18 | run: | 19 | echo "::set-output name=version::$(echo "$FULL_VERSION" | cut -f2 -d@)" 20 | - name: Generate the spec 21 | run: | 22 | yarn 23 | yarn build 24 | cd packages/expo-cli 25 | yarn --silent introspect fig > new-spec.ts 26 | - name: 'Create Autocomplete PR' 27 | uses: withfig/push-to-fig-autocomplete-action@v1 28 | with: 29 | token: ${{ secrets.GH_TOKEN }} 30 | autocomplete-spec-name: expo 31 | spec-path: packages/expo-cli/new-spec.ts 32 | diff-based-versioning: true 33 | new-spec-version: ${{ steps.echo-version.outputs.version }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .expo 4 | .eslintcache 5 | .tags* 6 | .git-crypt-key 7 | .npm 8 | .npm-cache 9 | npm-debug.log 10 | *~ 11 | *.log 12 | .vscode 13 | .idea/* 14 | *.swp 15 | *.swo 16 | *.tsbuildinfo 17 | .history 18 | 19 | packages/*/build/ 20 | unlinked-packages/*/build/ 21 | 22 | # Logs 23 | packages/*/logs/ 24 | *.log 25 | 26 | # Runtime data 27 | pids 28 | *.pid 29 | *.seed 30 | 31 | # Directory for instrumented libs generated by jscoverage/JSCover 32 | lib-cov 33 | 34 | # Coverage directory used by tools like istanbul 35 | coverage/ 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # this is the build folder for @expo/webpack-config for some reason 41 | packages/webpack-config/webpack 42 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore build artifacts 2 | /packages/config/build 3 | /packages/config/src/__tests__/fixtures/behavior/syntax-error 4 | /packages/dev-server/build 5 | /packages/dev-tools/.next 6 | /packages/dev-tools/build 7 | /packages/dev-tools/fragmentTypes.json 8 | /packages/expo-cli/build 9 | /packages/expo-codemod/build 10 | /packages/expo-optimize/build 11 | /packages/image-utils/build 12 | /packages/jest-preset-cli/build 13 | /packages/json-file/build 14 | /packages/json-file/__tests__/files 15 | /packages/metro-config/build 16 | /packages/next-adapter/build 17 | /packages/package-manager/build 18 | /packages/pkcs12/build 19 | /packages/plist/build 20 | /packages/pod-install/build 21 | /packages/pwa/build 22 | /packages/schemer/build 23 | /packages/traveling-fastlane/packaging/vendor 24 | /packages/traveling-fastlane/traveling-fastlane-* 25 | /packages/traveling-fastlane/publishing/darwin/dist 26 | /packages/traveling-fastlane/publishing/linux/dist 27 | /packages/uri-scheme/build 28 | /packages/xdl/build 29 | /packages/xdl/caches 30 | package.json 31 | 32 | # Generated 33 | .history 34 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "jsxBracketSameLine": true, 6 | "trailingComma": "es5", 7 | "arrowParens": "avoid", 8 | "endOfLine": "auto" 9 | } 10 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Code of Conduct 2 | 3 | The Expo team is committed to fostering an open and welcoming environment. 4 | 5 | **Our Community Guidelines can be found here:** 6 | 7 | https://expo.dev/guidelines 8 | -------------------------------------------------------------------------------- /assets/fig/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/android.png -------------------------------------------------------------------------------- /assets/fig/app-store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/app-store.png -------------------------------------------------------------------------------- /assets/fig/apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/apple.png -------------------------------------------------------------------------------- /assets/fig/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/block.png -------------------------------------------------------------------------------- /assets/fig/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/clear.png -------------------------------------------------------------------------------- /assets/fig/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/config.png -------------------------------------------------------------------------------- /assets/fig/customize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/customize.png -------------------------------------------------------------------------------- /assets/fig/dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/dev.png -------------------------------------------------------------------------------- /assets/fig/devices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/devices.png -------------------------------------------------------------------------------- /assets/fig/diagnostics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/diagnostics.png -------------------------------------------------------------------------------- /assets/fig/doctor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/doctor.png -------------------------------------------------------------------------------- /assets/fig/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/download.png -------------------------------------------------------------------------------- /assets/fig/eject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/eject.png -------------------------------------------------------------------------------- /assets/fig/expo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/expo.png -------------------------------------------------------------------------------- /assets/fig/export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/export.png -------------------------------------------------------------------------------- /assets/fig/false.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/false.png -------------------------------------------------------------------------------- /assets/fig/force.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/force.png -------------------------------------------------------------------------------- /assets/fig/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/help.png -------------------------------------------------------------------------------- /assets/fig/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/info.png -------------------------------------------------------------------------------- /assets/fig/init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/init.png -------------------------------------------------------------------------------- /assets/fig/install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/install.png -------------------------------------------------------------------------------- /assets/fig/key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/key.png -------------------------------------------------------------------------------- /assets/fig/lan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/lan.png -------------------------------------------------------------------------------- /assets/fig/latest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/latest.png -------------------------------------------------------------------------------- /assets/fig/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/list.png -------------------------------------------------------------------------------- /assets/fig/localhost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/localhost.png -------------------------------------------------------------------------------- /assets/fig/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/lock.png -------------------------------------------------------------------------------- /assets/fig/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/login.png -------------------------------------------------------------------------------- /assets/fig/logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/logout.png -------------------------------------------------------------------------------- /assets/fig/metro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/metro.png -------------------------------------------------------------------------------- /assets/fig/minify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/minify.png -------------------------------------------------------------------------------- /assets/fig/number.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/number.png -------------------------------------------------------------------------------- /assets/fig/offline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/offline.png -------------------------------------------------------------------------------- /assets/fig/path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/path.png -------------------------------------------------------------------------------- /assets/fig/play-store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/play-store.png -------------------------------------------------------------------------------- /assets/fig/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/play.png -------------------------------------------------------------------------------- /assets/fig/prebuild.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/prebuild.png -------------------------------------------------------------------------------- /assets/fig/publish-rollback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/publish-rollback.png -------------------------------------------------------------------------------- /assets/fig/publish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/publish.png -------------------------------------------------------------------------------- /assets/fig/push-clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/push-clear.png -------------------------------------------------------------------------------- /assets/fig/push.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/push.png -------------------------------------------------------------------------------- /assets/fig/quiet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/quiet.png -------------------------------------------------------------------------------- /assets/fig/register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/register.png -------------------------------------------------------------------------------- /assets/fig/scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/scheme.png -------------------------------------------------------------------------------- /assets/fig/send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/send.png -------------------------------------------------------------------------------- /assets/fig/skip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/skip.png -------------------------------------------------------------------------------- /assets/fig/string.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/string.png -------------------------------------------------------------------------------- /assets/fig/true.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/true.png -------------------------------------------------------------------------------- /assets/fig/tunnel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/tunnel.png -------------------------------------------------------------------------------- /assets/fig/upgrade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/upgrade.png -------------------------------------------------------------------------------- /assets/fig/url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/url.png -------------------------------------------------------------------------------- /assets/fig/verbose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/verbose.png -------------------------------------------------------------------------------- /assets/fig/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/web.png -------------------------------------------------------------------------------- /assets/fig/webhook-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/webhook-add.png -------------------------------------------------------------------------------- /assets/fig/webhook-remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/webhook-remove.png -------------------------------------------------------------------------------- /assets/fig/webhook-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/webhook-update.png -------------------------------------------------------------------------------- /assets/fig/webhook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/webhook.png -------------------------------------------------------------------------------- /assets/fig/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/webpack.png -------------------------------------------------------------------------------- /assets/fig/wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/assets/fig/wifi.png -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | require_ci_to_pass: yes 4 | 5 | coverage: 6 | round: up 7 | precision: 2 8 | range: '30...100' 9 | 10 | status: 11 | changes: no 12 | patch: yes 13 | project: no 14 | 15 | comment: false 16 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | projects: [ 3 | require('./packages/babel-preset-cli/jest.config'), 4 | require('./packages/config/jest.config'), 5 | require('./packages/dev-server/jest.config'), 6 | require('./packages/dev-tools/jest.config'), 7 | require('./packages/expo-cli/jest.config'), 8 | require('./packages/expo-codemod/jest.config'), 9 | require('./packages/expo-env-info/jest.config'), 10 | require('./packages/json-file/jest.config'), 11 | require('./packages/metro-config/jest.config'), 12 | require('./packages/package-manager/jest.config'), 13 | require('./packages/pkcs12/jest.config'), 14 | require('./packages/plist/jest.config'), 15 | require('./packages/pwa/jest.config'), 16 | require('./packages/schemer/jest.config'), 17 | require('./packages/uri-scheme/jest.config'), 18 | require('./packages/webpack-config/jest/unit-test-config'), 19 | require('./packages/xdl/jest/integration-test-config'), 20 | require('./unlinked-packages/configure-splash-screen/jest.config'), 21 | ], 22 | testPathIgnorePatterns: ['.*'], 23 | }; 24 | -------------------------------------------------------------------------------- /jest/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | // Only use this when running tests 5 | env: { 6 | test: { 7 | presets: ['../packages/babel-preset-cli'], 8 | }, 9 | }, 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /jest/unit-test-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | testRegex: '/__tests__/.*(test|spec)\\.[jt]sx?$', 4 | transform: { 5 | '^.+\\.[jt]sx?$': ['babel-jest', { configFile: require.resolve('./babel.config.js') }], 6 | }, 7 | watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'], 8 | }; 9 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "yarn", 3 | "registry": "https://registry.npmjs.org/", 4 | "useWorkspaces": true, 5 | "version": "independent", 6 | "packages": ["packages/*"], 7 | "command": { 8 | "publish": { 9 | "ignoreChanges": ["packages/config-types/**/*"] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/babel-preset-cli/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present 650 Industries, Inc. (aka Expo) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/babel-preset-cli/README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 👋 Welcome to
@expo/babel-preset-cli 4 |

5 | 6 |

A Babel preset used across Expo CLI packages.

7 | 8 |

9 | 10 | 11 | 12 | 13 | 14 |

15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/babel-preset-cli/index.js: -------------------------------------------------------------------------------- 1 | module.exports = () => ({ 2 | presets: [ 3 | [ 4 | require('@babel/preset-env'), 5 | { 6 | targets: { 7 | node: '12.0.0', 8 | }, 9 | modules: false, 10 | }, 11 | ], 12 | require('@babel/preset-typescript'), 13 | ], 14 | plugins: [ 15 | require('babel-plugin-dynamic-import-node'), 16 | require('@babel/plugin-proposal-export-namespace-from'), 17 | require('@babel/plugin-proposal-class-properties'), 18 | [ 19 | require('@babel/plugin-transform-modules-commonjs'), 20 | { 21 | lazy: /* istanbul ignore next */ source => true, 22 | }, 23 | ], 24 | require('@babel/plugin-proposal-optional-chaining'), 25 | require('@babel/plugin-proposal-nullish-coalescing-operator'), 26 | ], 27 | }); 28 | -------------------------------------------------------------------------------- /packages/babel-preset-cli/jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | preset: '../../jest/unit-test-config', 5 | rootDir: path.resolve(__dirname), 6 | displayName: require('./package').name, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/config-plugins/README.md: -------------------------------------------------------------------------------- 1 | # `@expo/config-plugins` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/%40expo/config-plugins). 4 | -------------------------------------------------------------------------------- /packages/config-types/README.md: -------------------------------------------------------------------------------- 1 | # `@expo/config-types` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/%40expo/config-types). 4 | -------------------------------------------------------------------------------- /packages/config/README.md: -------------------------------------------------------------------------------- 1 | # `@expo/config` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/%40expo/config). 4 | -------------------------------------------------------------------------------- /packages/create-expo-app/README.md: -------------------------------------------------------------------------------- 1 | # `create-expo-app` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/create-expo). 4 | -------------------------------------------------------------------------------- /packages/dev-server/README.md: -------------------------------------------------------------------------------- 1 | # `@expo/dev-server` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/%40expo/dev-server). -------------------------------------------------------------------------------- /packages/dev-tools/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['universe/node', 'universe/web'], 3 | rules: { 4 | 'react/jsx-fragments': 'off', 5 | }, 6 | ignorePatterns: ['**/*.js'], 7 | }; 8 | -------------------------------------------------------------------------------- /packages/dev-tools/.gitignore: -------------------------------------------------------------------------------- 1 | config.js 2 | .next 3 | secrets.json 4 | -------------------------------------------------------------------------------- /packages/dev-tools/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018-present, 650 Industries, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/dev-tools/README.md: -------------------------------------------------------------------------------- 1 | # @expo/dev-tools 2 | 3 | The Expo Dev Tools UI has been deprecated. [Learn more](https://blog.expo.dev/sunsetting-the-web-ui-for-expo-cli-ab12936d2206). 4 | -------------------------------------------------------------------------------- /packages/dev-tools/__mocks__/xdl.js: -------------------------------------------------------------------------------- 1 | const xdl = jest.genMockFromModule('xdl'); 2 | 3 | xdl.UrlUtils = { 4 | constructDeepLinkAsync(projectDir) { 5 | return this.constructManifestUrlAsync(projectDir); 6 | }, 7 | constructManifestUrlAsync(projectDir) { 8 | return 'exp://mock-manifest-url'; 9 | }, 10 | }; 11 | 12 | xdl.ProjectSettings = { 13 | readAsync(projectDir) { 14 | return Promise.resolve({ 15 | hostType: 'lan', 16 | }); 17 | }, 18 | getCurrentStatusAsync() { 19 | return Promise.resolve('running'); 20 | }, 21 | }; 22 | 23 | xdl.UserSettings = { 24 | readAsync() { 25 | return Promise.resolve({ 26 | sendTo: 'fake-send-to', 27 | }); 28 | }, 29 | }; 30 | 31 | xdl.Config = { 32 | offline: false, 33 | }; 34 | 35 | module.exports = xdl; 36 | -------------------------------------------------------------------------------- /packages/dev-tools/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = api => { 2 | api.cache(true); 3 | 4 | return { 5 | presets: ['next/babel'], 6 | plugins: [ 7 | ['emotion', { inline: true }], 8 | [ 9 | 'module-resolver', 10 | { 11 | alias: { 12 | app: '.', 13 | }, 14 | }, 15 | ], 16 | [require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }], 17 | ], 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /packages/dev-tools/common/createApolloClient.js: -------------------------------------------------------------------------------- 1 | import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; 2 | import { ApolloClient } from 'apollo-client'; 3 | import { WebSocketLink } from 'apollo-link-ws'; 4 | 5 | import introspectionQueryResultData from '../fragmentTypes.json'; 6 | 7 | export default function createApolloClient(subscriptionClient) { 8 | const fragmentMatcher = new IntrospectionFragmentMatcher({ 9 | introspectionQueryResultData, 10 | }); 11 | return new ApolloClient({ 12 | link: new WebSocketLink(subscriptionClient), 13 | cache: new InMemoryCache({ dataIdFromObject, fragmentMatcher }), 14 | }); 15 | } 16 | 17 | function dataIdFromObject(object) { 18 | // If the object has a field named `id`, it must be a globally unique ID. 19 | if (object.id !== undefined) { 20 | return object.id; 21 | } 22 | return null; 23 | } 24 | -------------------------------------------------------------------------------- /packages/dev-tools/common/sets.js: -------------------------------------------------------------------------------- 1 | export const swap = (a, x, y) => { 2 | if (a.length === 1) return a; 3 | const result = [...a]; 4 | result.splice(y, 1, result.splice(x, 1, result[y])[0]); 5 | return result; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/dev-tools/common/validations.js: -------------------------------------------------------------------------------- 1 | import * as Strings from 'app/common/strings'; 2 | 3 | export const EMAIL_REGEX = /^[a-z0-9\u007F-\uffff!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9\u007F-\uffff!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/i; 4 | 5 | export const PHONE_REGEX = /^\d{5,}$/; 6 | 7 | export const MIN_PASSWORD_LENGTH = 3; 8 | export const MIN_USERNAME_LENGTH = 1; 9 | export const MAX_USERNAME_LENGTH = 128; 10 | 11 | export const isPhoneNumber = phoneNumber => { 12 | if (Strings.isEmptyOrNull(phoneNumber)) { 13 | return false; 14 | } 15 | 16 | const testCase = phoneNumber.replace(/[\s()+\-.]|ext/gi, ''); 17 | 18 | if (!PHONE_REGEX.test(testCase)) { 19 | return false; 20 | } 21 | 22 | return true; 23 | }; 24 | 25 | export const isEmail = email => { 26 | if (Strings.isEmptyOrNull(email)) { 27 | return false; 28 | } 29 | 30 | if (!EMAIL_REGEX.test(email)) { 31 | return false; 32 | } 33 | 34 | return true; 35 | }; 36 | -------------------------------------------------------------------------------- /packages/dev-tools/components/ContentGroup.js: -------------------------------------------------------------------------------- 1 | import * as Constants from 'app/common/constants'; 2 | import React from 'react'; 3 | import { css } from 'react-emotion'; 4 | import { VelocityTransitionGroup } from 'velocity-react'; 5 | 6 | if (process.browser) { 7 | require('velocity-animate'); 8 | require('velocity-animate/velocity.ui'); 9 | } 10 | 11 | const STYLES_HEADER = css` 12 | width: 100%; 13 | `; 14 | 15 | const STYLES_CHILDREN = css` 16 | border-top: 1px solid ${Constants.colors.border}; 17 | font-family: ${Constants.fontFamilies.regular}; 18 | background: ${Constants.colors.sidebarBackground}; 19 | padding: 16px; 20 | `; 21 | 22 | export default class ContentGroup extends React.Component { 23 | render() { 24 | return ( 25 |
26 |
{this.props.header}
27 | 30 | {this.props.isActive ? ( 31 |
{this.props.children}
32 | ) : undefined} 33 |
34 |
35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/dev-tools/components/DeprecatedBanner.js: -------------------------------------------------------------------------------- 1 | import * as Constants from 'app/common/constants'; 2 | import React from 'react'; 3 | import { css } from 'react-emotion'; 4 | 5 | const STYLES_BANNER = css` 6 | padding: 16px; 7 | width: 100%; 8 | background-color: ${Constants.colors.red}; 9 | `; 10 | const STYLES_INDICATOR = css` 11 | font-family: ${Constants.fontFamilies.mono}; 12 | color: ${Constants.colors.white}; 13 | font-size: 12px; 14 | padding-top: 2px; 15 | `; 16 | 17 | export default function DeprecatedBanner() { 18 | return ( 19 |
20 | 21 | The Expo CLI Web UI is deprecated, please use the Terminal UI instead.{' '} 22 | 25 | Learn more 26 | 27 | . 28 | 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /packages/dev-tools/components/IndexPageErrors.js: -------------------------------------------------------------------------------- 1 | import * as Constants from 'app/common/constants'; 2 | import React from 'react'; 3 | import { css } from 'react-emotion'; 4 | 5 | const STYLES_ERROR_CONTAINER = css` 6 | background: ${Constants.colors.foreground}; 7 | position: relative; 8 | display: flex; 9 | flex-direction: column; 10 | align-items: flex-start; 11 | height: 100%; 12 | width: 100%; 13 | padding: 40px 60px; 14 | font-family: ${Constants.fontFamilies.regular}; 15 | color: ${Constants.colors.white}; 16 | `; 17 | 18 | const STYLE_MESSAGE = css` 19 | font-family: ${Constants.fontFamilies.mono}; 20 | color: ${Constants.colors.white}; 21 | line-height: 14px; 22 | overflow-wrap: break-word; 23 | white-space: pre-wrap; 24 | margin-top: 20px; 25 | `; 26 | 27 | const STYLES_ERROR = css` 28 | color: ${Constants.colors.red}; 29 | `; 30 | 31 | export default props => ( 32 |
33 |
Error loading DevTools
34 | {props.error.graphQLErrors.map(error => { 35 | return
{error.message}
; 36 | })} 37 |
38 | ); 39 | -------------------------------------------------------------------------------- /packages/dev-tools/components/Loader.js: -------------------------------------------------------------------------------- 1 | import * as Constants from 'app/common/constants'; 2 | import * as React from 'react'; 3 | import { css } from 'react-emotion'; 4 | 5 | const STYLES_LOADER = css` 6 | animation: rotate 1s infinite linear; 7 | display: inline-block; 8 | margin: 2px 0 0 0; 9 | `; 10 | 11 | export default class Loader extends React.Component { 12 | render() { 13 | return ( 14 | 21 | 25 | 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/dev-tools/components/LoggerIcon.js: -------------------------------------------------------------------------------- 1 | import * as SVG from 'app/common/svg'; 2 | import * as React from 'react'; 3 | 4 | export default props => { 5 | if (props.type === 'Process') { 6 | return ; 7 | } 8 | 9 | if (props.type === 'Device') { 10 | return ; 11 | } 12 | 13 | if (props.type === 'Issues') { 14 | return ; 15 | } 16 | 17 | return ; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/dev-tools/components/OptionsButton.js: -------------------------------------------------------------------------------- 1 | import * as Constants from 'app/common/constants'; 2 | import styled, { css } from 'react-emotion'; 3 | 4 | const OptionsButton = styled.span` 5 | font-family: ${Constants.fontFamilies.demi}; 6 | border-left: 1px solid ${Constants.colors.border}; 7 | flex-shrink: 0; 8 | cursor: pointer; 9 | padding: 0 16px 0 16px; 10 | height: 100%; 11 | display: flex; 12 | align-items: center; 13 | justify-content: center; 14 | 15 | ${props => (!props.isDisabled ? OptionsButtonHover : '')}; 16 | 17 | ${props => (props.isActive ? OptionsButtonActive : '')}; 18 | 19 | ${props => (props.isDisabled ? OptionsButtonDisabled : '')}; 20 | `; 21 | 22 | const OptionsButtonHover = css` 23 | :hover { 24 | background: ${Constants.colors.border}; 25 | } 26 | `; 27 | 28 | const OptionsButtonActive = css` 29 | box-shadow: inset 0 -2px 0 ${Constants.colors.primary}; 30 | border-left: 1px solid ${Constants.colors.border}; 31 | 32 | :hover { 33 | background: ${Constants.colors.white}; 34 | } 35 | `; 36 | 37 | const OptionsButtonDisabled = css` 38 | cursor: default; 39 | color: ${Constants.colors.border}; 40 | `; 41 | 42 | export default OptionsButton; 43 | -------------------------------------------------------------------------------- /packages/dev-tools/components/UserIndicator.js: -------------------------------------------------------------------------------- 1 | import * as Constants from 'app/common/constants'; 2 | import React from 'react'; 3 | import { css } from 'react-emotion'; 4 | 5 | const STYLES_INDICATOR = css` 6 | font-family: ${Constants.fontFamilies.mono}; 7 | color: ${Constants.colors.darkText}; 8 | font-size: 10px; 9 | text-transform: uppercase; 10 | padding-top: 2px; 11 | `; 12 | 13 | const STYLES_USERNAME = css` 14 | color: ${Constants.colors.green}; 15 | `; 16 | 17 | export default class UserIndicator extends React.Component { 18 | render() { 19 | if (!this.props.user) { 20 | return Logged out; 21 | } 22 | return ( 23 | 24 | Logged in as {this.props.user.username} 25 | 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/dev-tools/fragmentTypes.json: -------------------------------------------------------------------------------- 1 | {"__schema":{"types":[{"kind":"INTERFACE","name":"Source","possibleTypes":[{"name":"Issues"},{"name":"Process"},{"name":"Device"}]},{"kind":"INTERFACE","name":"Message","possibleTypes":[{"name":"Issue"},{"name":"LogMessage"},{"name":"DeviceMessage"},{"name":"MetroInitializeStarted"},{"name":"BuildProgress"},{"name":"BuildFinished"},{"name":"BuildError"},{"name":"TunnelReady"}]},{"kind":"UNION","name":"OpenSimulatorResult","possibleTypes":[{"name":"OpenSimulatorSuccess"},{"name":"OpenSimulatorError"}]},{"kind":"UNION","name":"MessageSubscriptionPayload","possibleTypes":[{"name":"MessagePayload"},{"name":"SourceClearedPayload"}]}]}} -------------------------------------------------------------------------------- /packages/dev-tools/jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | preset: '../../jest/unit-test-config', 5 | rootDir: path.resolve(__dirname), 6 | displayName: require('./package').name, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/dev-tools/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /packages/dev-tools/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | exportPathMap(defaultPathMap) { 3 | return { 4 | '/': { page: '/' }, 5 | }; 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/dev-tools/server/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@expo/babel-preset-cli'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/dev-tools/server/extract-fragment-types.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | // @ts-ignore 3 | import { graphql } from 'graphql'; 4 | 5 | import GraphQLSchema from './graphql/GraphQLSchema'; 6 | 7 | graphql( 8 | GraphQLSchema, 9 | ` 10 | { 11 | __schema { 12 | types { 13 | kind 14 | name 15 | possibleTypes { 16 | name 17 | } 18 | } 19 | } 20 | } 21 | ` 22 | ).then((result: any) => { 23 | // here we're filtering out any type information unrelated to unions or interfaces 24 | const filteredData = result.data.__schema.types.filter( 25 | (type: any) => type.possibleTypes !== null 26 | ); 27 | result.data.__schema.types = filteredData; 28 | fs.writeFile('./fragmentTypes.json', JSON.stringify(result.data), err => { 29 | if (err) { 30 | console.error('Error writing fragmentTypes file', err); 31 | } else { 32 | console.log('Fragment types successfully extracted!'); 33 | } 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/dev-tools/server/graphql/Issues.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | 3 | export type Issue = Record; 4 | 5 | export default class Issues extends EventEmitter { 6 | issues: Record = {}; 7 | 8 | addIssue(issueId: string, issue: Issue): void { 9 | let newIssue = false; 10 | if (!this.issues[issueId]) { 11 | newIssue = true; 12 | } 13 | this.issues[issueId] = { 14 | ...issue, 15 | id: issueId, 16 | }; 17 | if (newIssue) { 18 | this.emit('ADDED', this.issues[issueId]); 19 | } else { 20 | this.emit('UPDATED', this.issues[issueId]); 21 | } 22 | } 23 | 24 | clearIssue(issueId: string): void { 25 | const issue = this.issues[issueId]; 26 | if (issue) { 27 | delete this.issues[issueId]; 28 | this.emit('DELETED', issue); 29 | } 30 | } 31 | 32 | getIssueList(): { cursor: string; node: Issue }[] { 33 | return Object.keys(this.issues).map(key => ({ 34 | cursor: key, 35 | node: this.issues[key], 36 | })); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/dev-tools/server/index.ts: -------------------------------------------------------------------------------- 1 | import * as DevToolsServer from './DevToolsServer'; 2 | 3 | export { DevToolsServer }; 4 | -------------------------------------------------------------------------------- /packages/dev-tools/static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /packages/dev-tools/static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/apple-touch-icon.png -------------------------------------------------------------------------------- /packages/dev-tools/static/default-project-icons/bordered-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/default-project-icons/bordered-1.png -------------------------------------------------------------------------------- /packages/dev-tools/static/default-project-icons/bordered-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/default-project-icons/bordered-10.png -------------------------------------------------------------------------------- /packages/dev-tools/static/default-project-icons/bordered-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/default-project-icons/bordered-11.png -------------------------------------------------------------------------------- /packages/dev-tools/static/default-project-icons/bordered-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/default-project-icons/bordered-12.png -------------------------------------------------------------------------------- /packages/dev-tools/static/default-project-icons/bordered-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/default-project-icons/bordered-13.png -------------------------------------------------------------------------------- /packages/dev-tools/static/default-project-icons/bordered-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/default-project-icons/bordered-14.png -------------------------------------------------------------------------------- /packages/dev-tools/static/default-project-icons/bordered-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/default-project-icons/bordered-15.png -------------------------------------------------------------------------------- /packages/dev-tools/static/default-project-icons/bordered-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/default-project-icons/bordered-2.png -------------------------------------------------------------------------------- /packages/dev-tools/static/default-project-icons/bordered-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/default-project-icons/bordered-3.png -------------------------------------------------------------------------------- /packages/dev-tools/static/default-project-icons/bordered-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/default-project-icons/bordered-4.png -------------------------------------------------------------------------------- /packages/dev-tools/static/default-project-icons/bordered-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/default-project-icons/bordered-5.png -------------------------------------------------------------------------------- /packages/dev-tools/static/default-project-icons/bordered-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/default-project-icons/bordered-6.png -------------------------------------------------------------------------------- /packages/dev-tools/static/default-project-icons/bordered-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/default-project-icons/bordered-7.png -------------------------------------------------------------------------------- /packages/dev-tools/static/default-project-icons/bordered-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/default-project-icons/bordered-8.png -------------------------------------------------------------------------------- /packages/dev-tools/static/default-project-icons/bordered-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/default-project-icons/bordered-9.png -------------------------------------------------------------------------------- /packages/dev-tools/static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/favicon-16x16.png -------------------------------------------------------------------------------- /packages/dev-tools/static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/favicon-32x32.png -------------------------------------------------------------------------------- /packages/dev-tools/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/favicon.ico -------------------------------------------------------------------------------- /packages/dev-tools/static/fonts/MaisonNeue-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/fonts/MaisonNeue-Bold.woff -------------------------------------------------------------------------------- /packages/dev-tools/static/fonts/MaisonNeue-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/fonts/MaisonNeue-Bold.woff2 -------------------------------------------------------------------------------- /packages/dev-tools/static/fonts/MaisonNeue-Book.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/fonts/MaisonNeue-Book.woff -------------------------------------------------------------------------------- /packages/dev-tools/static/fonts/MaisonNeue-Book.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/fonts/MaisonNeue-Book.woff2 -------------------------------------------------------------------------------- /packages/dev-tools/static/fonts/MaisonNeue-BookItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/fonts/MaisonNeue-BookItalic.woff -------------------------------------------------------------------------------- /packages/dev-tools/static/fonts/MaisonNeue-BookItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/fonts/MaisonNeue-BookItalic.woff2 -------------------------------------------------------------------------------- /packages/dev-tools/static/fonts/MaisonNeue-Demi.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/fonts/MaisonNeue-Demi.woff -------------------------------------------------------------------------------- /packages/dev-tools/static/fonts/MaisonNeue-Demi.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/fonts/MaisonNeue-Demi.woff2 -------------------------------------------------------------------------------- /packages/dev-tools/static/fonts/MaisonNeue-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/fonts/MaisonNeue-Light.woff -------------------------------------------------------------------------------- /packages/dev-tools/static/fonts/MaisonNeue-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/fonts/MaisonNeue-Light.woff2 -------------------------------------------------------------------------------- /packages/dev-tools/static/fonts/MaisonNeueMono-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/fonts/MaisonNeueMono-Regular.woff -------------------------------------------------------------------------------- /packages/dev-tools/static/fonts/MaisonNeueMono-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/fonts/MaisonNeueMono-Regular.woff2 -------------------------------------------------------------------------------- /packages/dev-tools/static/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/dev-tools/static/mstile-150x150.png -------------------------------------------------------------------------------- /packages/dev-tools/static/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "theme_color": "#ffffff", 12 | "background_color": "#ffffff", 13 | "display": "standalone" 14 | } 15 | -------------------------------------------------------------------------------- /packages/dev-tools/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base", 3 | "include": [ 4 | "server/**/*.ts" 5 | ], 6 | "exclude": [ 7 | "**/__mocks__/*", 8 | "**/__tests__/*" 9 | ], 10 | "compilerOptions": { 11 | "composite": false, 12 | "declaration": false, 13 | "noImplicitAny": false, 14 | "outDir": "build/server", 15 | "rootDir": "server", 16 | "allowJs": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "noEmit": false, 19 | "isolatedModules": true, 20 | "jsx": "preserve" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/electron-adapter/README.md: -------------------------------------------------------------------------------- 1 | # `@expo/electron-adapter` 2 | 3 | This package has [moved to the `expo/expo-webpack-integrations` repo](https://github.com/expo/expo-webpack-integrations/tree/main/packages/electron-adapter#readme). 4 | -------------------------------------------------------------------------------- /packages/expo-cli/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'no-console': 'error', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/expo-cli/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 650 Industries 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /packages/expo-cli/__mocks__/@expo/image-utils.ts: -------------------------------------------------------------------------------- 1 | export async function generateImageAsync(input: any, { src }) { 2 | const fs = require('fs'); 3 | return { source: fs.readFileSync(src) }; 4 | } 5 | 6 | export async function compositeImagesAsync({ foreground }) { 7 | return foreground; 8 | } 9 | -------------------------------------------------------------------------------- /packages/expo-cli/__mocks__/@expo/rudder-sdk-node.ts: -------------------------------------------------------------------------------- 1 | export default jest.fn().mockImplementation(() => ({ 2 | logger: jest.fn(), 3 | identify: jest.fn(), 4 | track: jest.fn(), 5 | })); 6 | -------------------------------------------------------------------------------- /packages/expo-cli/__mocks__/fs.js: -------------------------------------------------------------------------------- 1 | import { fs } from 'memfs'; 2 | module.exports = fs; 3 | -------------------------------------------------------------------------------- /packages/expo-cli/__mocks__/ora.js: -------------------------------------------------------------------------------- 1 | const ora = jest.fn(() => { 2 | return { 3 | start: jest.fn(() => { 4 | return { stop: jest.fn(), succeed: jest.fn(), fail: jest.fn() }; 5 | }), 6 | stop: jest.fn(), 7 | stopAndPersist: jest.fn(), 8 | succeed: jest.fn(), 9 | fail: jest.fn(), 10 | }; 11 | }); 12 | 13 | module.exports = ora; 14 | -------------------------------------------------------------------------------- /packages/expo-cli/__mocks__/resolve-from.js: -------------------------------------------------------------------------------- 1 | function mockedResolveFrom(fromDirectory, request) { 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | try { 6 | fromDirectory = fs.realpathSync(fromDirectory); 7 | } catch (error) { 8 | if (error.code === 'ENOENT') { 9 | fromDirectory = path.resolve(fromDirectory); 10 | } else { 11 | return; 12 | } 13 | } 14 | 15 | const outputPath = path.join(fromDirectory, 'node_modules', request); 16 | if (fs.existsSync(outputPath)) { 17 | return outputPath; 18 | } 19 | } 20 | 21 | module.exports = jest.fn(mockedResolveFrom); 22 | module.exports.silent = jest.fn(mockedResolveFrom); 23 | -------------------------------------------------------------------------------- /packages/expo-cli/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ['@expo/babel-preset-cli'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/expo-cli/bin/expo.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | function yellow(text) { 4 | return '\u001b[33m' + text + '\u001b[39m'; 5 | } 6 | 7 | const match = /v(\d+)\.(\d+)/.exec(process.version); 8 | const major = parseInt(match[1], 10); 9 | 10 | // If newer than the current release 11 | if (major > 16) { 12 | // eslint-disable-next-line no-console 13 | console.warn( 14 | yellow( 15 | 'WARNING: The legacy expo-cli does not support Node +17. Migrate to the new local Expo CLI: https://blog.expo.dev/the-new-expo-cli-f4250d8e3421' 16 | ) 17 | ); 18 | } 19 | 20 | require('../build/exp.js').run('expo'); 21 | -------------------------------------------------------------------------------- /packages/expo-cli/e2e/fixtures/basic/App.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import React from 'react'; 3 | import { View } from 'react-native'; 4 | 5 | export default function App() { 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /packages/expo-cli/e2e/fixtures/basic/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "android": { 4 | "package": "com.example.minimal" 5 | }, 6 | "ios": { 7 | "bundleIdentifier": "com.example.minimal" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /packages/expo-cli/e2e/fixtures/basic/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/expo-cli/e2e/fixtures/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "android": "expo start --android", 7 | "ios": "expo start --ios", 8 | "web": "expo start --web", 9 | "start": "expo start" 10 | }, 11 | "dependencies": { 12 | "expo": "~43.0.0", 13 | "react": "17.0.1", 14 | "react-native": "0.64.3" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "^7.12.9" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/expo-cli/jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const enableE2E = process.env.CI || process.env.E2E; 4 | 5 | const roots = ['__mocks__', 'src']; 6 | 7 | if (enableE2E) { 8 | roots.push('e2e'); 9 | } 10 | 11 | module.exports = { 12 | preset: '../../jest/unit-test-config', 13 | rootDir: path.resolve(__dirname), 14 | displayName: require('./package').name, 15 | roots, 16 | setupFilesAfterEnv: ['/jest/setup.ts'], 17 | testTimeout: 60000, 18 | testRunner: 'jest-jasmine2', 19 | }; 20 | -------------------------------------------------------------------------------- /packages/expo-cli/jest/setup.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const originalError = console.error; 4 | const originalWarn = console.warn; 5 | const originalLog = console.log; 6 | 7 | beforeAll(() => { 8 | console.error = jest.fn(); 9 | console.warn = jest.fn(); 10 | console.log = jest.fn(); 11 | }); 12 | 13 | afterAll(() => { 14 | console.error = originalError; 15 | console.warn = originalWarn; 16 | console.log = originalLog; 17 | }); 18 | 19 | jest.mock('@expo/rudder-sdk-node'); 20 | -------------------------------------------------------------------------------- /packages/expo-cli/src/__tests__/mock-utils.ts: -------------------------------------------------------------------------------- 1 | import * as xdl from 'xdl'; 2 | 3 | type MockedXDLModules = Partial>; 4 | 5 | function mockExpoXDL(mockedXDLModules: MockedXDLModules): void { 6 | jest.mock('xdl', () => { 7 | const pkg = jest.requireActual('xdl'); 8 | const xdlMock = { ...pkg }; 9 | for (const [name, value] of Object.entries(mockedXDLModules)) { 10 | xdlMock[name] = { 11 | ...pkg[name], 12 | ...value, 13 | }; 14 | } 15 | return xdlMock; 16 | }); 17 | } 18 | 19 | export { mockExpoXDL }; 20 | -------------------------------------------------------------------------------- /packages/expo-cli/src/__tests__/user-fixtures.ts: -------------------------------------------------------------------------------- 1 | import { User } from 'xdl'; 2 | 3 | const jester: User = { 4 | kind: 'user', 5 | username: 'jester', 6 | nickname: 'jester', 7 | userId: 'jester-id', 8 | picture: 'jester-pic', 9 | userMetadata: { onboarded: true }, 10 | currentConnection: 'Username-Password-Authentication', 11 | sessionSecret: 'jester-secret', 12 | }; 13 | 14 | export { jester }; 15 | -------------------------------------------------------------------------------- /packages/expo-cli/src/analytics/StatusEventEmitter.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | 3 | interface BundleBuildFinishEvent { 4 | totalBuildTimeMs: number; 5 | } 6 | interface DeviceLogReceiveEvent { 7 | deviceId: string; 8 | deviceName: string; 9 | } 10 | interface StatusEvents { 11 | bundleBuildFinish: BundleBuildFinishEvent; 12 | deviceLogReceive: DeviceLogReceiveEvent; 13 | } 14 | type StatusEventKey = keyof StatusEvents; 15 | 16 | declare interface StatusEventEmitter { 17 | addListener( 18 | event: K, 19 | listener: (fields: StatusEvents[K]) => void 20 | ): this; 21 | once(event: K, listener: (fields: StatusEvents[K]) => void): this; 22 | removeListener( 23 | event: K, 24 | listener: (fields: StatusEvents[K]) => void 25 | ): this; 26 | emit(event: K, fields: StatusEvents[K]): boolean; 27 | } 28 | 29 | class StatusEventEmitter extends EventEmitter {} 30 | 31 | export default new StatusEventEmitter(); 32 | -------------------------------------------------------------------------------- /packages/expo-cli/src/appleApi/createManagers.ts: -------------------------------------------------------------------------------- 1 | import { AppleCtx } from './authenticate'; 2 | import { DistCertManager } from './distributionCert'; 3 | import { ProvisioningProfileManager } from './provisioningProfile'; 4 | import { PushKeyManager } from './pushKey'; 5 | 6 | export const createManagers = (ctx: AppleCtx) => ({ 7 | distributionCert: new DistCertManager(ctx), 8 | pushKey: new PushKeyManager(ctx), 9 | provisioningProfile: new ProvisioningProfileManager(ctx), 10 | }); 11 | -------------------------------------------------------------------------------- /packages/expo-cli/src/appleApi/index.ts: -------------------------------------------------------------------------------- 1 | export * from './authenticate'; 2 | export * from './distributionCert'; 3 | export * from './pushKey'; 4 | export * from './provisioningProfile'; 5 | export * from './ensureAppExists'; 6 | export * from './createManagers'; 7 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/auth/login.ts: -------------------------------------------------------------------------------- 1 | import type { Command } from 'commander'; 2 | 3 | import { applyAnyAsyncAction } from '../utils/applyAsyncAction'; 4 | 5 | export default function (program: Command) { 6 | applyAnyAsyncAction( 7 | program 8 | .command('login') 9 | .description('Login to an Expo account') 10 | .alias('signin') 11 | .helpGroup('auth') 12 | .option('-u, --username [string]', 'Username') 13 | .option('-p, --password [string]', 'Password') 14 | .option('--otp [string]', 'One-time password from your 2FA device'), 15 | () => import('./loginAsync') 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/auth/loginAsync.ts: -------------------------------------------------------------------------------- 1 | import { login } from './accounts'; 2 | 3 | export const actionAsync = login; 4 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/auth/logout.ts: -------------------------------------------------------------------------------- 1 | import type { Command } from 'commander'; 2 | 3 | import { applyAnyAsyncAction } from '../utils/applyAsyncAction'; 4 | 5 | export default function (program: Command) { 6 | applyAnyAsyncAction( 7 | program.command('logout').description('Logout of an Expo account').helpGroup('auth'), 8 | () => import('./logoutAsync') 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/auth/logoutAsync.ts: -------------------------------------------------------------------------------- 1 | import { UserManager } from 'xdl'; 2 | 3 | import CommandError from '../../CommandError'; 4 | import Log from '../../log'; 5 | 6 | export async function actionAsync() { 7 | const user = await UserManager.getCurrentUserAsync({ silent: true }); 8 | if (user?.accessToken) { 9 | throw new CommandError( 10 | 'ACCESS_TOKEN_ERROR', 11 | 'Please remove the EXPO_TOKEN environment var to logout.' 12 | ); 13 | } 14 | 15 | try { 16 | await UserManager.logoutAsync(); 17 | Log.log('Logged out'); 18 | } catch (e: any) { 19 | throw new CommandError(`Couldn't logout: ${e.message}`); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/auth/register.ts: -------------------------------------------------------------------------------- 1 | import type { Command } from 'commander'; 2 | 3 | import { applyAnyAsyncAction } from '../utils/applyAsyncAction'; 4 | 5 | export default function (program: Command) { 6 | applyAnyAsyncAction( 7 | program.command('register').helpGroup('auth').description('Sign up for a new Expo account'), 8 | () => import('./registerAsync') 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/auth/registerAsync.ts: -------------------------------------------------------------------------------- 1 | import CommandError from '../../CommandError'; 2 | import { openRegistrationInBrowser, REGISTRATION_URL } from './accounts'; 3 | 4 | type Options = { 5 | parent?: { 6 | nonInteractive: boolean; 7 | }; 8 | }; 9 | 10 | export async function actionAsync(options: Options) { 11 | if (options.parent?.nonInteractive) { 12 | throw new CommandError( 13 | 'NON_INTERACTIVE', 14 | `Run the command without the '--non-interactive' flag or visit ${REGISTRATION_URL} to register a new account.` 15 | ); 16 | } 17 | 18 | openRegistrationInBrowser(); 19 | } 20 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/auth/whoami.ts: -------------------------------------------------------------------------------- 1 | import type { Command } from 'commander'; 2 | 3 | import { applyAnyAsyncAction } from '../utils/applyAsyncAction'; 4 | 5 | export default function (program: Command) { 6 | applyAnyAsyncAction( 7 | program 8 | .command('whoami') 9 | .helpGroup('auth') 10 | .alias('w') 11 | .description('Return the currently authenticated account'), 12 | () => import('./whoamiAsync') 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/auth/whoamiAsync.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import { UserManager } from 'xdl'; 3 | 4 | import Log from '../../log'; 5 | 6 | export type Options = { 7 | parent?: { 8 | nonInteractive: boolean; 9 | }; 10 | }; 11 | 12 | export async function actionAsync(command: Options) { 13 | const user = await UserManager.getCurrentUserAsync({ silent: true }); 14 | if (user) { 15 | if (command.parent?.nonInteractive) { 16 | Log.nested(user.username); 17 | } else { 18 | Log.log(`Logged in as ${chalk.cyan(user.username)}`); 19 | } 20 | } else { 21 | Log.log(`\u203A Not logged in, run ${chalk.cyan`expo login`} to authenticate`); 22 | process.exit(1); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/build/BaseBuilder.types.ts: -------------------------------------------------------------------------------- 1 | export type CommonOptions = { 2 | clearCredentials: boolean; 3 | publicUrl?: string; 4 | releaseChannel: string; 5 | publish: boolean; 6 | wait: boolean; 7 | skipWorkflowCheck?: boolean; 8 | parent?: { nonInteractive: boolean }; 9 | config?: string; 10 | }; 11 | 12 | export type IosOptions = CommonOptions & { 13 | type: 'archive' | 'simulator'; 14 | clearDistCert: boolean; 15 | clearPushKey: boolean; 16 | clearPushCert: boolean; 17 | clearProvisioningProfile: boolean; 18 | revokeCredentials: boolean; 19 | appleId?: string; 20 | teamId?: string; 21 | distP12Path?: string; 22 | pushP12Path?: string; 23 | pushId?: string; 24 | pushP8Path?: string; 25 | provisioningProfilePath?: string; 26 | skipCredentialsCheck?: boolean; 27 | }; 28 | 29 | export type AndroidOptions = CommonOptions & { 30 | type: 'app-bundle' | 'apk'; 31 | keystorePath?: string; 32 | keystoreAlias?: string; 33 | generateKeystore: boolean; 34 | skipCredentialsCheck?: boolean; 35 | }; 36 | 37 | export type BuilderOptions = Omit & Partial, 'type'> & { 38 | type?: string; 39 | }; 40 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/build/BuildError.ts: -------------------------------------------------------------------------------- 1 | export default class BuildError extends Error { 2 | readonly name = 'BuildError'; 3 | 4 | constructor(public message: string) { 5 | super(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/build/buildAndroidAsync.ts: -------------------------------------------------------------------------------- 1 | import program from 'commander'; 2 | 3 | import Log from '../../log'; 4 | import AndroidBuilder from './AndroidBuilder'; 5 | import type { AndroidOptions } from './BaseBuilder.types'; 6 | import { logBuildMigration } from './logBuildMigration'; 7 | import { assertPublicUrl, assertReleaseChannel, maybeBailOnWorkflowWarning } from './utils'; 8 | 9 | export async function actionAsync(projectRoot: string, options: AndroidOptions) { 10 | logBuildMigration('android'); 11 | 12 | if (options.generateKeystore) { 13 | Log.warn( 14 | `The --generate-keystore flag is deprecated and does not do anything. A Keystore will always be generated on the Expo servers if it's missing.` 15 | ); 16 | } 17 | if (!options.skipWorkflowCheck) { 18 | if ( 19 | await maybeBailOnWorkflowWarning({ 20 | projectRoot, 21 | platform: 'android', 22 | nonInteractive: program.nonInteractive, 23 | }) 24 | ) { 25 | return; 26 | } 27 | } 28 | 29 | assertPublicUrl(options.publicUrl); 30 | assertReleaseChannel(options.releaseChannel); 31 | 32 | const androidBuilder = new AndroidBuilder(projectRoot, options); 33 | return androidBuilder.command(); 34 | } 35 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/build/buildIosAsync.ts: -------------------------------------------------------------------------------- 1 | import program from 'commander'; 2 | 3 | import CommandError from '../../CommandError'; 4 | import type { IosOptions } from './BaseBuilder.types'; 5 | import IOSBuilder from './ios/IOSBuilder'; 6 | import { logBuildMigration } from './logBuildMigration'; 7 | import { assertPublicUrl, assertReleaseChannel, maybeBailOnWorkflowWarning } from './utils'; 8 | 9 | export async function actionAsync(projectRoot: string, options: IosOptions) { 10 | logBuildMigration('ios'); 11 | if (!options.skipWorkflowCheck) { 12 | if ( 13 | await maybeBailOnWorkflowWarning({ 14 | projectRoot, 15 | platform: 'ios', 16 | nonInteractive: program.nonInteractive, 17 | }) 18 | ) { 19 | return; 20 | } 21 | } 22 | if (options.skipCredentialsCheck && options.clearCredentials) { 23 | throw new CommandError( 24 | "--skip-credentials-check and --clear-credentials can't be used together" 25 | ); 26 | } 27 | assertPublicUrl(options.publicUrl); 28 | assertReleaseChannel(options.releaseChannel); 29 | 30 | const iosBuilder = new IOSBuilder(projectRoot, options); 31 | return iosBuilder.command(); 32 | } 33 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/build/buildStatusAsync.ts: -------------------------------------------------------------------------------- 1 | import BaseBuilder from './BaseBuilder'; 2 | import { assertPublicUrl } from './utils'; 3 | 4 | type Options = { 5 | publicUrl?: string; 6 | }; 7 | 8 | export async function actionAsync(projectRoot: string, options: Options) { 9 | assertPublicUrl(options.publicUrl); 10 | const builder = new BaseBuilder(projectRoot, options); 11 | return builder.commandCheckStatus(); 12 | } 13 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/build/buildWebAsync.ts: -------------------------------------------------------------------------------- 1 | import { Webpack } from 'xdl'; 2 | 3 | import { warnAboutLocalCLI } from '../../utils/migration'; 4 | 5 | type Options = { 6 | pwa?: boolean; 7 | clear?: boolean; 8 | dev?: boolean; 9 | }; 10 | 11 | export async function actionAsync(projectRoot: string, options: Options) { 12 | warnAboutLocalCLI(projectRoot, { localCmd: 'export:web' }); 13 | 14 | return Webpack.bundleAsync(projectRoot, { 15 | ...options, 16 | dev: typeof options.dev === 'undefined' ? false : options.dev, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/build/constants.ts: -------------------------------------------------------------------------------- 1 | export type Platform = 'ios' | 'android' | 'web' | 'all'; 2 | 3 | export const PLATFORMS: { 4 | IOS: 'ios'; 5 | ANDROID: 'android'; 6 | WEB: 'web'; 7 | ALL: 'all'; 8 | } = { 9 | IOS: 'ios', 10 | ANDROID: 'android', 11 | WEB: 'web', 12 | ALL: 'all', 13 | }; 14 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/build/findReusableBuildAsync.ts: -------------------------------------------------------------------------------- 1 | import { ApiV2, UserManager } from 'xdl'; 2 | 3 | export async function findReusableBuildAsync( 4 | releaseChannel: string, 5 | platform: string, 6 | sdkVersion: string, 7 | slug: string, 8 | owner?: string 9 | ): Promise<{ downloadUrl?: string; canReuse: boolean }> { 10 | const user = await UserManager.getCurrentUserAsync(); 11 | 12 | const buildReuseStatus = await ApiV2.clientForUser(user).postAsync('standalone-build/reuse', { 13 | releaseChannel, 14 | platform, 15 | sdkVersion, 16 | slug, 17 | owner, 18 | }); 19 | 20 | return buildReuseStatus; 21 | } 22 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/build/getLatestReleaseAsync.ts: -------------------------------------------------------------------------------- 1 | import { getConfig } from '@expo/config'; 2 | import { ApiV2, UserManager } from 'xdl'; 3 | 4 | type Release = { 5 | fullName: string; 6 | channel: string; 7 | channelId: string; 8 | publicationId: string; 9 | appVersion: string; 10 | sdkVersion: string; 11 | publishedTime: string; 12 | platform: string; 13 | }; 14 | 15 | export async function getLatestReleaseAsync( 16 | projectRoot: string, 17 | options: { 18 | releaseChannel: string; 19 | platform: string; 20 | owner?: string; 21 | } 22 | ): Promise { 23 | const user = await UserManager.ensureLoggedInAsync(); 24 | const api = ApiV2.clientForUser(user); 25 | const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); 26 | const result = await api.postAsync('publish/history', { 27 | owner: options.owner, 28 | slug: exp.slug, 29 | releaseChannel: options.releaseChannel, 30 | count: 1, 31 | platform: options.platform, 32 | }); 33 | const { queryResult } = result; 34 | if (queryResult && queryResult.length > 0) { 35 | return queryResult[0]; 36 | } else { 37 | return null; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/client/clientIosAsync.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import Log from '../../log'; 4 | import * as TerminalLink from '../utils/TerminalLink'; 5 | 6 | export async function actionAsync() { 7 | Log.newLine(); 8 | 9 | Log.log( 10 | chalk.yellow( 11 | `${chalk.bold(`expo client:ios`)} has been replaced by ${chalk.bold`Expo Dev Clients`}.\n` 12 | ) + chalk.dim(TerminalLink.learnMore(`https://docs.expo.dev/development/getting-started`)) 13 | ); 14 | 15 | Log.newLine(); 16 | } 17 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/credentials.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import type { Command } from 'commander'; 3 | 4 | import { applyAsyncActionProjectDir } from './utils/applyAsyncAction'; 5 | 6 | export default function (program: Command) { 7 | applyAsyncActionProjectDir( 8 | program 9 | .command('credentials:manager [path]') 10 | .description(`${chalk.yellow`Superseded`} by ${chalk.bold`eas credentials`} in eas-cli`) 11 | .helpGroup('credentials') 12 | .option('-p --platform ', 'Platform: [android|ios]', /^(android|ios)$/i), 13 | () => import('./credentialsManagerAsync'), 14 | { 15 | checkConfig: false, 16 | skipSDKVersionRequirement: true, 17 | } 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/credentialsManagerAsync.ts: -------------------------------------------------------------------------------- 1 | import { Context, runCredentialsManagerStandalone } from '../credentials'; 2 | import { 3 | SelectAndroidExperience, 4 | SelectIosExperience, 5 | SelectPlatform, 6 | } from '../credentials/views/Select'; 7 | 8 | type Options = { 9 | platform?: 'android' | 'ios'; 10 | parent?: { 11 | nonInteractive: boolean; 12 | }; 13 | }; 14 | 15 | export async function actionAsync(projectRoot: string, options: Options) { 16 | const context = new Context(); 17 | await context.init(projectRoot, { 18 | nonInteractive: options.parent?.nonInteractive, 19 | }); 20 | let mainpage; 21 | if (options.platform === 'android') { 22 | mainpage = new SelectAndroidExperience(); 23 | } else if (options.platform === 'ios') { 24 | mainpage = new SelectIosExperience(); 25 | } else { 26 | mainpage = new SelectPlatform(); 27 | } 28 | await runCredentialsManagerStandalone(context, mainpage); 29 | } 30 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/eject/__tests__/platformOptions-test.ts: -------------------------------------------------------------------------------- 1 | import { ensureValidPlatforms } from '../platformOptions'; 2 | 3 | describe(ensureValidPlatforms, () => { 4 | const platform = process.platform; 5 | 6 | afterEach(() => { 7 | Object.defineProperty(process, 'platform', { 8 | value: platform, 9 | }); 10 | }); 11 | it(`bails on windows if only ios is passed`, async () => { 12 | Object.defineProperty(process, 'platform', { 13 | value: 'win32', 14 | }); 15 | expect(ensureValidPlatforms(['ios', 'android'])).toStrictEqual(['android']); 16 | }); 17 | it(`allows ios on all platforms except windows`, async () => { 18 | Object.defineProperty(process, 'platform', { 19 | value: 'other', 20 | }); 21 | expect(ensureValidPlatforms(['ios', 'android'])).toStrictEqual(['ios', 'android']); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/eject/customize.ts: -------------------------------------------------------------------------------- 1 | import type { Command } from 'commander'; 2 | 3 | import { applyAsyncActionProjectDir } from '../utils/applyAsyncAction'; 4 | 5 | export default function (program: Command) { 6 | applyAsyncActionProjectDir( 7 | program 8 | .command('customize:web [path]') 9 | .description('Eject the default web files for customization') 10 | .helpGroup('eject') 11 | .option('-f, --force', 'Allows replacing existing files') 12 | .allowOffline(), 13 | () => import('./customizeAsync') 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/eject/eject.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import { Command } from 'commander'; 3 | 4 | import { applyAsyncActionProjectDir } from '../utils/applyAsyncAction'; 5 | 6 | export default function (program: Command) { 7 | applyAsyncActionProjectDir( 8 | program 9 | .command('eject [path]') 10 | .description(`${chalk.yellow`Superseded`} by ${chalk.bold`expo prebuild`}`) 11 | .longDescription( 12 | 'Create Xcode and Android Studio projects for your app. Use this if you need to add custom native functionality.' 13 | ) 14 | .helpGroup('deprecated') 15 | .option('--no-install', 'Skip installing npm packages and CocoaPods.') 16 | .option('--npm', 'Use npm to install dependencies. (default when Yarn is not installed)') 17 | .option( 18 | '-p, --platform ', 19 | 'Platforms to sync: ios, android, all. Default: all' 20 | ), 21 | () => import('./ejectAsync') 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/eject/resolveTemplate.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import { isURL } from 'xdl/build/UrlUtils'; 4 | 5 | import CommandError from '../../CommandError'; 6 | 7 | export function resolveTemplateOption(template: string) { 8 | if (isURL(template, {})) { 9 | return template; 10 | } 11 | 12 | if (template.startsWith(`.${path.sep}`) || template.startsWith(path.sep)) { 13 | const templatePath = path.resolve(template); 14 | if (!fs.existsSync(templatePath)) { 15 | throw new CommandError('template file does not exist: ' + templatePath); 16 | } 17 | if (!fs.statSync(templatePath).isFile()) { 18 | throw new CommandError( 19 | 'template must be a tar file created by running `npm pack` in a project: ' + templatePath 20 | ); 21 | } 22 | return templatePath; 23 | } 24 | return template; 25 | } 26 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/expokit/bundle-assets.ts: -------------------------------------------------------------------------------- 1 | import type { Command } from 'commander'; 2 | 3 | import { applyAsyncActionProjectDir } from '../utils/applyAsyncAction'; 4 | 5 | export default function (program: Command) { 6 | applyAsyncActionProjectDir( 7 | program 8 | .command('bundle-assets [path]') 9 | .description( 10 | 'Bundle assets for a detached app. This command should be executed from xcode or gradle' 11 | ) 12 | .helpGroup('internal') 13 | .option('--dest [dest]', 'Destination directory for assets') 14 | .option('--platform ', 'detached project platform'), 15 | () => import('./bundleAssetsAsync') 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/expokit/bundleAssetsAsync.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import terminalLink from 'terminal-link'; 3 | import { Detach } from 'xdl'; 4 | 5 | import { SilentError } from '../../CommandError'; 6 | import Log from '../../log'; 7 | 8 | type Options = { 9 | dest?: string; 10 | platform?: string; 11 | }; 12 | 13 | export async function actionAsync(projectRoot: string, options: Options) { 14 | try { 15 | await Detach.bundleAssetsAsync(projectRoot, options); 16 | } catch (e: any) { 17 | Log.error(e); 18 | Log.error( 19 | `Before making a release build, make sure you have run '${chalk.bold( 20 | 'expo publish' 21 | )}' at least once. ${terminalLink( 22 | 'Learn more.', 23 | 'https://expo.fyi/release-builds-with-expo-updates' 24 | )}` 25 | ); 26 | throw new SilentError(e); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/expokit/prepare-detached-build.ts: -------------------------------------------------------------------------------- 1 | import type { Command } from 'commander'; 2 | 3 | import { applyAsyncActionProjectDir } from '../utils/applyAsyncAction'; 4 | 5 | export default function (program: Command) { 6 | applyAsyncActionProjectDir( 7 | program 8 | .command('prepare-detached-build [path]') 9 | .description('Prepare a detached project for building') 10 | .helpGroup('internal') 11 | .option('--platform ', 'detached project platform') 12 | .option('--skipXcodeConfig [bool]', '[iOS only] if true, do not configure Xcode project'), 13 | () => import('./prepareDetachedBuildAsync') 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/expokit/prepareDetachedBuildAsync.ts: -------------------------------------------------------------------------------- 1 | import { Detach } from 'xdl'; 2 | 3 | type Options = { 4 | platform?: string; 5 | skipXcodeConfig: boolean; 6 | }; 7 | 8 | export async function actionAsync(projectRoot: string, options: Options) { 9 | await Detach.prepareDetachedBuildAsync(projectRoot, options); 10 | } 11 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/export/__tests__/__snapshots__/writeContents-test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`writeDebugHtmlAsync creates a debug html file 1`] = ` 4 | " 5 | 6 | 7 | 8 | Open up this file in Chrome. In the JavaScript developer console, navigate to the Source tab. 9 | You can see a red colored folder containing the original source code from your bundle. 10 | " 11 | `; 12 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/export/__tests__/createMetadataJson-test.ts: -------------------------------------------------------------------------------- 1 | import { createMetadataJson } from '../createMetadataJson'; 2 | 3 | describe(createMetadataJson, () => { 4 | it(`writes metadata manifest`, async () => { 5 | const metadata = await createMetadataJson({ 6 | fileNames: { 7 | ios: 'ios-xxfooxxbarxx.js', 8 | }, 9 | bundles: { 10 | ios: { 11 | assets: [{ type: 'image', fileHashes: ['foobar', 'other'] }], 12 | }, 13 | }, 14 | }); 15 | 16 | expect(metadata).toStrictEqual({ 17 | bundler: expect.any(String), 18 | fileMetadata: { 19 | ios: { 20 | assets: [ 21 | { 22 | ext: 'image', 23 | path: 'assets/foobar', 24 | }, 25 | { 26 | ext: 'image', 27 | path: 'assets/other', 28 | }, 29 | ], 30 | bundle: 'bundles/ios-xxfooxxbarxx.js', 31 | }, 32 | }, 33 | version: expect.any(Number), 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/fetch/fetchAndroidKeystoreAsync.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { Context } from '../../credentials'; 4 | import { runCredentialsManager } from '../../credentials/route'; 5 | import { DownloadKeystore } from '../../credentials/views/AndroidKeystore'; 6 | import { assertSlug, maybeRenameExistingFileAsync, Options } from './utils'; 7 | 8 | export async function actionAsync(projectRoot: string, options: Options): Promise { 9 | const ctx = new Context(); 10 | await ctx.init(projectRoot, { 11 | nonInteractive: options.parent?.nonInteractive, 12 | }); 13 | 14 | const keystoreFilename = `${ctx.manifest.slug}.jks`; 15 | await maybeRenameExistingFileAsync(projectRoot, keystoreFilename); 16 | const backupKeystoreOutputPath = path.resolve(projectRoot, keystoreFilename); 17 | const experienceName = `@${ctx.projectOwner}/${ctx.manifest.slug}`; 18 | 19 | assertSlug(ctx.manifest.slug); 20 | await runCredentialsManager( 21 | ctx, 22 | new DownloadKeystore(experienceName, { 23 | outputPath: backupKeystoreOutputPath, 24 | displayCredentials: true, 25 | }) 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/fetch/utils.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import chalk from 'chalk'; 3 | import * as fs from 'fs-extra'; 4 | import * as path from 'path'; 5 | 6 | import Log from '../../log'; 7 | 8 | export type Options = { 9 | parent?: { 10 | nonInteractive: boolean; 11 | }; 12 | }; 13 | 14 | export function assertSlug(slug: any): asserts slug { 15 | assert(slug, `${chalk.bold(slug)} field must be set in your app.json or app.config.js`); 16 | } 17 | 18 | export async function maybeRenameExistingFileAsync(projectRoot: string, filename: string) { 19 | const desiredFilePath = path.resolve(projectRoot, filename); 20 | 21 | if (await fs.pathExists(desiredFilePath)) { 22 | let num = 1; 23 | while (await fs.pathExists(path.resolve(projectRoot, `OLD_${num}_${filename}`))) { 24 | num++; 25 | } 26 | Log.log( 27 | `\nA file already exists at "${desiredFilePath}"\n Renaming the existing file to OLD_${num}_${filename}\n` 28 | ); 29 | await fs.rename(desiredFilePath, path.resolve(projectRoot, `OLD_${num}_${filename}`)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/index.ts: -------------------------------------------------------------------------------- 1 | import type { Command } from 'commander'; 2 | 3 | const COMMANDS = [ 4 | require('./auth/login'), 5 | require('./auth/logout'), 6 | require('./auth/register'), 7 | require('./auth/whoami'), 8 | require('./build'), 9 | require('./credentials'), 10 | require('./eject/customize'), 11 | require('./eject/eject'), 12 | require('./eject/prebuild'), 13 | require('./expokit/bundle-assets'), 14 | require('./expokit/prepare-detached-build'), 15 | require('./export/export'), 16 | require('./fetch'), 17 | require('./info/config/config'), 18 | require('./info/doctor'), 19 | require('./info/upgrade'), 20 | require('./init'), 21 | require('./install'), 22 | require('./publish/publish'), 23 | require('./push'), 24 | require('./run'), 25 | require('./send'), 26 | require('./start'), 27 | require('./upload'), 28 | // Moved this to below upload for the ordering in the help command. 29 | // eslint-disable-next-line import/order 30 | require('./client'), 31 | require('./url'), 32 | require('./webhooks'), 33 | ]; 34 | 35 | export function registerCommands(program: Command) { 36 | COMMANDS.forEach(commandModule => { 37 | commandModule.default(program); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/info/config/config.ts: -------------------------------------------------------------------------------- 1 | import type { Command } from 'commander'; 2 | 3 | import { applyAsyncActionProjectDir } from '../../utils/applyAsyncAction'; 4 | 5 | export default function (program: Command) { 6 | applyAsyncActionProjectDir( 7 | program 8 | .command('config [path]') 9 | .description('Show the project config') 10 | .helpGroup('info') 11 | .option('-t, --type ', 'Type of config to show.') 12 | .option('--full', 'Include all project config data') 13 | .option('--json', 'Output in JSON format'), 14 | () => import('./configAsync') 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/info/doctor/depedencies/__tests__/explain.test.ts: -------------------------------------------------------------------------------- 1 | import { printExplanationsAsync } from '../explain'; 2 | 3 | describe(printExplanationsAsync, () => { 4 | it(`formats`, async () => { 5 | await printExplanationsAsync( 6 | { 7 | name: '@expo/config-plugins', 8 | version: '~4.1.4', 9 | }, 10 | require('./fixtures/invalid-plugins.json') 11 | ); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/info/doctor/index.ts: -------------------------------------------------------------------------------- 1 | import type { Command } from 'commander'; 2 | 3 | import { applyAsyncActionProjectDir } from '../../utils/applyAsyncAction'; 4 | 5 | export default function (program: Command) { 6 | applyAsyncActionProjectDir( 7 | program 8 | .command('doctor [path]') 9 | .description('Diagnose issues with the project') 10 | .helpGroup('info') 11 | .option('--fix-dependencies', 'Fix incompatible dependency versions'), 12 | () => import('./doctorAsync') 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/info/upgrade.ts: -------------------------------------------------------------------------------- 1 | import type { Command } from 'commander'; 2 | 3 | import { applyAsyncAction } from '../utils/applyAsyncAction'; 4 | 5 | export default function (program: Command) { 6 | applyAsyncAction( 7 | program 8 | .command('upgrade [sdk-version]') 9 | .alias('update') 10 | .description('Upgrade the project packages and config for the given SDK version') 11 | .helpGroup('info') 12 | .option('--npm', 'Use npm to install dependencies. (default when package-lock.json exists)') 13 | .option('--yarn', 'Use Yarn to install dependencies. (default when yarn.lock exists)'), 14 | () => import('./upgradeAsync') 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/init.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import type { Command } from 'commander'; 3 | 4 | import { applyAsyncAction } from './utils/applyAsyncAction'; 5 | 6 | export default function (program: Command) { 7 | applyAsyncAction( 8 | program 9 | .command('init [name]') 10 | .alias('i') 11 | .helpGroup('core') 12 | .description('Create a new Expo project') 13 | .option( 14 | '-t, --template [name]', 15 | 'Specify which template to use. Valid options are "blank", "tabs", "bare-minimum" or a package on npm (e.g. "expo-template-bare-minimum") that includes an Expo project template.' 16 | ) 17 | .option('--npm', 'Use npm to install dependencies. (default when Yarn is not installed)') 18 | .option('--no-install', 'Skip installing npm packages or CocoaPods.') 19 | .option('--name ', chalk`{yellow Deprecated}: Use {bold expo init [name]} instead.`) 20 | .option('--yes', 'Use default options. Same as "expo init . --template blank') 21 | .option('--yarn', 'Use Yarn to install dependencies. (default when Yarn is installed)'), 22 | () => import('./initAsync') 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/install.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import type { Command } from 'commander'; 3 | 4 | import Log from '../log'; 5 | import { applyAsyncAction } from './utils/applyAsyncAction'; 6 | 7 | export default function (program: Command) { 8 | applyAsyncAction( 9 | program 10 | .command('install [packages...]') 11 | .alias('add') 12 | .helpGroup('core') 13 | .option('--npm', 'Use npm to install dependencies. (default when package-lock.json exists)') 14 | .option('--yarn', 'Use Yarn to install dependencies. (default when yarn.lock exists)') 15 | .description('Install a module or other package to a project') 16 | .on('--help', () => { 17 | Log.log( 18 | ` Additional options can be passed to the ${chalk.green('npm install')} or ${chalk.green( 19 | 'yarn add' 20 | )} command by using ${chalk.green('--')}` 21 | ); 22 | Log.log(` For example: ${chalk.green('expo install somepackage -- --verbose')}`); 23 | }), 24 | () => import('./installAsync') 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/publish/deprecationNotice.ts: -------------------------------------------------------------------------------- 1 | import Log from '../../log'; 2 | import { learnMore } from '../utils/TerminalLink'; 3 | 4 | export async function printDeprecationNotice(): Promise { 5 | Log.warn( 6 | 'The last day to use expo publish is 2024-02-12 and SDK 49 is the last version to support it. Migrate to eas update.' 7 | ); 8 | Log.warn( 9 | `${learnMore('https://blog.expo.dev/sunsetting-expo-publish-and-classic-updates-6cb9cd295378')}` 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/publish/publishDetailsAsync.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | import { 4 | DetailOptions, 5 | getPublicationDetailAsync, 6 | printPublicationDetailAsync, 7 | } from '../utils/PublishUtils'; 8 | import { printDeprecationNotice } from './deprecationNotice'; 9 | 10 | export async function actionAsync(projectRoot: string, options: DetailOptions) { 11 | printDeprecationNotice(); 12 | assert(options.publishId, '--publish-id must be specified.'); 13 | 14 | const detail = await getPublicationDetailAsync(projectRoot, options); 15 | await printPublicationDetailAsync(detail, options); 16 | } 17 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/publish/publishSetAsync.ts: -------------------------------------------------------------------------------- 1 | import * as table from '../../commands/utils/cli-table'; 2 | import Log from '../../log'; 3 | import { setPublishToChannelAsync } from '../utils/PublishUtils'; 4 | import { printDeprecationNotice } from './deprecationNotice'; 5 | 6 | type Options = { releaseChannel?: string; publishId?: string }; 7 | 8 | export async function actionAsync(projectRoot: string, options: Options): Promise { 9 | printDeprecationNotice(); 10 | if (!options.releaseChannel) { 11 | throw new Error('You must specify a release channel.'); 12 | } 13 | if (!options.publishId) { 14 | throw new Error('You must specify a publish id. You can find ids using publish:history.'); 15 | } 16 | try { 17 | const result = await setPublishToChannelAsync( 18 | projectRoot, 19 | options as { releaseChannel: string; publishId: string } 20 | ); 21 | const tableString = table.printTableJson(result.queryResult, 'Channel Set Status ', 'SUCCESS'); 22 | Log.log(tableString); 23 | } catch (e: any) { 24 | Log.error(e); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/push.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import type { Command } from 'commander'; 3 | 4 | import { applyAsyncActionProjectDir } from './utils/applyAsyncAction'; 5 | 6 | export default function (program: Command) { 7 | applyAsyncActionProjectDir( 8 | program 9 | .command('push:android:upload [path]') 10 | .description(`${chalk.yellow`Superseded`} by ${chalk.bold`eas credentials`} in eas-cli`) 11 | .helpGroup('deprecated') 12 | .option('--api-key [api-key]', 'Server API key for FCM.'), 13 | () => import('./push/pushAndroidUploadAsync') 14 | ); 15 | 16 | applyAsyncActionProjectDir( 17 | program 18 | .command('push:android:show [path]') 19 | .description(`${chalk.yellow`Superseded`} by ${chalk.bold`eas credentials`} in eas-cli`) 20 | .helpGroup('deprecated'), 21 | () => import('./push/pushAndroidShowAsync') 22 | ); 23 | 24 | applyAsyncActionProjectDir( 25 | program 26 | .command('push:android:clear [path]') 27 | .description(`${chalk.yellow`Superseded`} by ${chalk.bold`eas credentials`} in eas-cli`) 28 | .helpGroup('deprecated'), 29 | () => import('./push/pushAndroidClearAsync') 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/push/pushAndroidClearAsync.ts: -------------------------------------------------------------------------------- 1 | import { Context } from '../../credentials/context'; 2 | import Log from '../../log'; 3 | 4 | export async function actionAsync(projectRoot: string) { 5 | const ctx = new Context(); 6 | await ctx.init(projectRoot); 7 | const experienceName = `@${ctx.projectOwner}/${ctx.manifest.slug}`; 8 | 9 | await ctx.android.removeFcmKey(experienceName); 10 | Log.log('All done!'); 11 | } 12 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/push/pushAndroidShowAsync.ts: -------------------------------------------------------------------------------- 1 | import CommandError from '../../CommandError'; 2 | import { Context } from '../../credentials/context'; 3 | import Log from '../../log'; 4 | 5 | export async function actionAsync(projectRoot: string) { 6 | const ctx = new Context(); 7 | await ctx.init(projectRoot); 8 | const experienceName = `@${ctx.projectOwner}/${ctx.manifest.slug}`; 9 | 10 | const fcmCredentials = await ctx.android.fetchFcmKey(experienceName); 11 | if (fcmCredentials?.fcmApiKey) { 12 | Log.log(`FCM API key: ${fcmCredentials?.fcmApiKey}`); 13 | } else { 14 | throw new CommandError(`There is no FCM API key configured for this project`); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/push/pushAndroidUploadAsync.ts: -------------------------------------------------------------------------------- 1 | import { Context } from '../../credentials/context'; 2 | import Log from '../../log'; 3 | 4 | type Options = { apiKey?: string }; 5 | 6 | export async function actionAsync(projectRoot: string, options: Options) { 7 | if (!options.apiKey || options.apiKey.length === 0) { 8 | throw new Error('Must specify an API key to upload with --api-key.'); 9 | } 10 | 11 | const ctx = new Context(); 12 | await ctx.init(projectRoot); 13 | const experienceName = `@${ctx.projectOwner}/${ctx.manifest.slug}`; 14 | 15 | await ctx.android.updateFcmKey(experienceName, options.apiKey); 16 | Log.log('All done!'); 17 | } 18 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/run/ios/startBundlerAsync.ts: -------------------------------------------------------------------------------- 1 | import { Project, ProjectSettings } from 'xdl'; 2 | 3 | import * as TerminalUI from '../../start/TerminalUI'; 4 | import { installExitHooks } from '../../start/installExitHooks'; 5 | import { getOptionalDevClientSchemeAsync } from '../utils/schemes'; 6 | 7 | export async function setGlobalDevClientSettingsAsync(projectRoot: string) { 8 | const devClient = true; 9 | const scheme = await getOptionalDevClientSchemeAsync(projectRoot).catch(() => null); 10 | await ProjectSettings.setAsync(projectRoot, { 11 | devClient, 12 | scheme, 13 | }); 14 | } 15 | 16 | export async function startBundlerAsync( 17 | projectRoot: string, 18 | { metroPort, platforms }: Pick 19 | ) { 20 | // Add clean up hooks 21 | installExitHooks(projectRoot); 22 | // This basically means don't use the Client app. 23 | const devClient = true; 24 | await Project.startAsync(projectRoot, { devClient, metroPort }); 25 | await TerminalUI.startAsync(projectRoot, { 26 | devClient, 27 | // Enable controls 28 | isWebSocketsEnabled: true, 29 | isRemoteReloadingEnabled: true, 30 | platforms, 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/run/utils/__tests__/Security-test.ts: -------------------------------------------------------------------------------- 1 | import { extractCodeSigningInfo, extractSigningId } from '../Security'; 2 | 3 | describe(extractCodeSigningInfo, () => { 4 | it(`extracts`, () => { 5 | expect( 6 | extractCodeSigningInfo( 7 | ' 2) 12312234253761286351826735HGKDHAJGF45283 "Apple Development: bacon@expo.io (BB00AABB0A)"' 8 | ) 9 | ).toBe('Apple Development: bacon@expo.io (BB00AABB0A)'); 10 | }); 11 | it(`does not match lines ending in (CSSMERR_TP_CERT_REVOKED)`, () => { 12 | expect( 13 | extractCodeSigningInfo( 14 | ' 3) 12442234253761286351826735HGKDHAJGF45283 "iPhone Distribution: Evan Bacon (CC00AABB0B)" (CSSMERR_TP_CERT_REVOKED)' 15 | ) 16 | ).toBe(null); 17 | }); 18 | }); 19 | 20 | describe(extractSigningId, () => { 21 | it(`extracts`, () => { 22 | expect(extractSigningId('Apple Development: Evan Bacon (AA00AABB0A)')).toBe('AA00AABB0A'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/run/utils/binaryPlist.ts: -------------------------------------------------------------------------------- 1 | import plist from '@expo/plist'; 2 | // @ts-ignore 3 | import binaryPlist from 'bplist-parser'; 4 | import fs from 'fs-extra'; 5 | 6 | import Log from '../../../log'; 7 | 8 | const CHAR_CHEVRON_OPEN = 60; 9 | const CHAR_B_LOWER = 98; 10 | // .mobileprovision 11 | // const CHAR_ZERO = 30; 12 | 13 | export async function parseBinaryPlistAsync(plistPath: string) { 14 | Log.debug(`Parse plist: ${plistPath}`); 15 | 16 | return parsePlistBuffer(await fs.readFile(plistPath)); 17 | } 18 | 19 | export function parsePlistBuffer(contents: Buffer) { 20 | if (contents[0] === CHAR_CHEVRON_OPEN) { 21 | const info = plist.parse(contents.toString()); 22 | if (Array.isArray(info)) return info[0]; 23 | return info; 24 | } else if (contents[0] === CHAR_B_LOWER) { 25 | const info = binaryPlist.parseBuffer(contents); 26 | if (Array.isArray(info)) return info[0]; 27 | return info; 28 | } else { 29 | throw new Error(`Cannot parse plist of type byte (0x${contents[0].toString(16)})`); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/send.ts: -------------------------------------------------------------------------------- 1 | import type { Command } from 'commander'; 2 | 3 | import { applyAsyncActionProjectDir } from './utils/applyAsyncAction'; 4 | 5 | export default function (program: Command) { 6 | applyAsyncActionProjectDir( 7 | program 8 | .command('send [path]') 9 | .description(`Share the project's URL to an email address`) 10 | .helpGroup('core') 11 | .option('-s, --send-to [dest]', 'Email address to send the URL to') 12 | .urlOpts(), 13 | () => import('./sendAsync') 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/sendAsync.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import { UrlUtils, UserSettings } from 'xdl'; 3 | 4 | import Log from '../log'; 5 | import * as sendTo from './utils/sendTo'; 6 | import urlOpts, { URLOptions } from './utils/urlOpts'; 7 | 8 | type Options = Partial; 9 | 10 | export async function actionAsync(projectRoot: string, options: Options) { 11 | await urlOpts.optsAsync(projectRoot, options); 12 | 13 | const url = await UrlUtils.constructDeepLinkAsync(projectRoot); 14 | 15 | Log.nested('Project manifest URL\n\n' + chalk.underline(url) + '\n'); 16 | 17 | if (await urlOpts.handleMobileOptsAsync(projectRoot, options)) { 18 | return; 19 | } 20 | 21 | let recipient = 22 | typeof options.sendTo !== 'boolean' 23 | ? options.sendTo 24 | : await UserSettings.getAsync('sendTo', null); 25 | 26 | if (!recipient) { 27 | recipient = await sendTo.askForSendToAsync(); 28 | } 29 | 30 | await sendTo.sendUrlAsync(url, recipient); 31 | } 32 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/start/__tests__/parseStartOptions-test.ts: -------------------------------------------------------------------------------- 1 | import { parseRawArguments, setBooleanArg } from '../parseStartOptions'; 2 | 3 | describe(setBooleanArg, () => { 4 | it(`uses fallback`, () => { 5 | expect(setBooleanArg('dev', [], true)).toBe(true); 6 | }); 7 | it(`uses true value first`, () => { 8 | expect(setBooleanArg('dev', ['--no-dev', '--dev'])).toBe(true); 9 | }); 10 | it(`uses false value`, () => { 11 | expect(setBooleanArg('dev', ['--no-dev'])).toBe(false); 12 | }); 13 | }); 14 | 15 | describe(parseRawArguments, () => { 16 | it(`args overwrite incoming options`, () => { 17 | expect( 18 | parseRawArguments( 19 | { 20 | dev: true, 21 | }, 22 | ['--no-dev'] 23 | ).dev 24 | ).toBe(false); 25 | 26 | expect( 27 | parseRawArguments( 28 | { 29 | dev: false, 30 | }, 31 | [] 32 | ).dev 33 | ).toBe(true); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/url/logArtifactUrl.ts: -------------------------------------------------------------------------------- 1 | import { UrlUtils } from 'xdl'; 2 | 3 | import CommandError from '../../CommandError'; 4 | import Log from '../../log'; 5 | import { BuildJobFields, getBuildStatusAsync } from '../build/getBuildStatusAsync'; 6 | 7 | type ArtifactUrlOptions = { 8 | publicUrl?: string; 9 | }; 10 | 11 | function assertHTTPS(url?: string) { 12 | if (url && !UrlUtils.isHttps(url)) { 13 | throw new CommandError('INVALID_PUBLIC_URL', '--public-url must be a valid HTTPS URL.'); 14 | } 15 | } 16 | 17 | export const logArtifactUrl = (platform: 'ios' | 'android') => async ( 18 | projectRoot: string, 19 | options: ArtifactUrlOptions 20 | ) => { 21 | assertHTTPS(options.publicUrl); 22 | 23 | const result = await getBuildStatusAsync(projectRoot, { 24 | current: false, 25 | ...(options.publicUrl ? { publicUrl: options.publicUrl } : {}), 26 | }); 27 | 28 | const url = result.jobs?.filter((job: BuildJobFields) => job.platform === platform)[0]?.artifacts 29 | ?.url; 30 | 31 | if (!url) { 32 | throw new CommandError( 33 | `No ${platform} binary file found. Use "expo build:${platform}" to create one.` 34 | ); 35 | } 36 | 37 | Log.nested(url); 38 | }; 39 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/url/urlApkAsync.ts: -------------------------------------------------------------------------------- 1 | import { logArtifactUrl } from './logArtifactUrl'; 2 | 3 | export const actionAsync = logArtifactUrl('android'); 4 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/url/urlIpaAsync.ts: -------------------------------------------------------------------------------- 1 | import { logArtifactUrl } from './logArtifactUrl'; 2 | 3 | export const actionAsync = logArtifactUrl('ios'); 4 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/utils/Tar.ts: -------------------------------------------------------------------------------- 1 | import got from 'got'; 2 | import stream from 'stream'; 3 | import tar from 'tar'; 4 | import { promisify } from 'util'; 5 | 6 | import { createProgressTracker } from './progress'; 7 | 8 | const pipeline = promisify(stream.pipeline); 9 | 10 | /** 11 | * Download a tar.gz file and extract it to a folder. 12 | * 13 | * @param url remote URL to download. 14 | * @param destination destination folder to extract the tar to. 15 | */ 16 | export async function downloadAndDecompressAsync( 17 | url: string, 18 | destination: string 19 | ): Promise { 20 | const downloadStream = got.stream(url).on('downloadProgress', createProgressTracker()); 21 | 22 | await pipeline(downloadStream, tar.extract({ cwd: destination })); 23 | return destination; 24 | } 25 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/utils/__tests__/autoAddConfigPluginsAsync-test.ts: -------------------------------------------------------------------------------- 1 | import { getNamedPlugins } from '../autoAddConfigPluginsAsync'; 2 | 3 | describe(getNamedPlugins, () => { 4 | it('gets named plugins', () => { 5 | expect( 6 | getNamedPlugins([ 7 | 'bacon', 8 | '@evan/bacon', 9 | '@evan/bacon/foobar.js', 10 | ['./avocado.js', null], 11 | // @ts-ignore 12 | ['invalid', null, null], 13 | // @ts-ignore 14 | c => c, 15 | // @ts-ignore 16 | false, 17 | // @ts-ignore 18 | [c => c, null], 19 | ]) 20 | ).toStrictEqual(['bacon', '@evan/bacon', '@evan/bacon/foobar.js', './avocado.js']); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/utils/__tests__/npm-test.ts: -------------------------------------------------------------------------------- 1 | import { sanitizeNpmPackageName } from '../npm'; 2 | 3 | describe(sanitizeNpmPackageName, () => { 4 | it(`leaves valid names`, () => { 5 | for (const name of ['@bacon/app', 'my-app', 'my-a.pp', 'test123']) { 6 | expect(sanitizeNpmPackageName(name)).toBe(name); 7 | } 8 | }); 9 | it(`sanitizes invalid names`, () => { 10 | for (const [before, after] of [ 11 | ['..__..f_f', 'f_f'], 12 | ['_f', 'f'], 13 | ['Hello World', 'helloworld'], 14 | ['\u2665', 'love'], 15 | ['あいう', 'app'], 16 | ]) { 17 | expect(sanitizeNpmPackageName(before)).toBe(after); 18 | } 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/utils/__tests__/url-test.ts: -------------------------------------------------------------------------------- 1 | import { constructBuildLogsUrl } from '../url'; 2 | 3 | describe(constructBuildLogsUrl, () => { 4 | it('returns URL with project path', () => { 5 | const result = constructBuildLogsUrl({ 6 | username: 'test-username', 7 | projectSlug: 'test-project-slug', 8 | buildId: 'test-build-id', 9 | }); 10 | 11 | expect(result).toBe( 12 | 'https://expo.dev/accounts/test-username/projects/test-project-slug/builds/test-build-id' 13 | ); 14 | }); 15 | 16 | it('returns URL with account', () => { 17 | const result = constructBuildLogsUrl({ 18 | username: 'test-username', 19 | projectSlug: undefined, 20 | buildId: 'test-build-id', 21 | }); 22 | 23 | expect(result).toBe('https://expo.dev/accounts/test-username/builds/test-build-id'); 24 | }); 25 | it('returns URL without account or project slug', () => { 26 | const result = constructBuildLogsUrl({ 27 | username: undefined, 28 | projectSlug: undefined, 29 | buildId: 'test-build-id', 30 | }); 31 | 32 | expect(result).toBe('https://expo.dev/builds/test-build-id'); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/utils/__tests__/validateApplicationId-test.ts: -------------------------------------------------------------------------------- 1 | import { validateBundleId } from '../validateApplicationId'; 2 | 3 | describe(validateBundleId, () => { 4 | it(`validates`, () => { 5 | expect(validateBundleId('bacon')).toBe(true); 6 | expect(validateBundleId('...b.a.-c.0.n...')).toBe(true); 7 | expect(validateBundleId('.')).toBe(true); 8 | expect(validateBundleId('. ..')).toBe(false); 9 | expect(validateBundleId('_')).toBe(false); 10 | expect(validateBundleId(',')).toBe(false); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/utils/environment.ts: -------------------------------------------------------------------------------- 1 | import program from 'commander'; 2 | 3 | export function isNonInteractive() { 4 | return program.nonInteractive; 5 | } 6 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/utils/fetch-cache/response.ts: -------------------------------------------------------------------------------- 1 | import { CacheObject } from 'cacache'; 2 | import { BodyInit, Response, ResponseInit } from 'node-fetch'; 3 | 4 | const responseInternalSymbol = Object.getOwnPropertySymbols(new Response())[1]; 5 | 6 | export class NFCResponse extends Response { 7 | constructor( 8 | bodyStream?: BodyInit, 9 | metaData?: ResponseInit, 10 | public ejectFromCache: () => Promise<[CacheObject, CacheObject]> = function ejectFromCache( 11 | this: any 12 | ) { 13 | return this.ejectSelfFromCache(); 14 | }, 15 | public fromCache: boolean = false 16 | ) { 17 | super(bodyStream, metaData); 18 | } 19 | 20 | static serializeMetaFromNodeFetchResponse(res: Response) { 21 | const metaData = { 22 | url: res.url, 23 | status: res.status, 24 | statusText: res.statusText, 25 | headers: res.headers.raw(), 26 | size: res.size, 27 | timeout: res.timeout, 28 | // @ts-ignore 29 | counter: res[responseInternalSymbol].counter, 30 | }; 31 | 32 | return metaData; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/utils/logConfigWarnings.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import * as TerminalLink from './TerminalLink'; 4 | 5 | export function formatNamedWarning(property: string, warning: string, link?: string) { 6 | return `- ${chalk.bold(property)}: ${warning}${ 7 | link ? getSpacer(warning) + chalk.dim(TerminalLink.learnMore(link)) : '' 8 | }`; 9 | } 10 | 11 | function getSpacer(text: string) { 12 | if (text.endsWith('.')) { 13 | return ' '; 14 | } else { 15 | return '. '; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/utils/profileMethod.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import Log from '../../log'; 4 | 5 | /** 6 | * Wrap a method and profile the time it takes to execute the method using `EXPO_PROFILE`. 7 | * Works best with named functions (i.e. not arrow functions). 8 | * 9 | * @param fn 10 | * @param functionName 11 | */ 12 | export const profileMethod = (fn: (...args: T) => U, functionName?: string) => { 13 | const name = chalk.dim(`⏱ [profile] ${functionName ?? (fn.name || 'unknown')}`); 14 | return (...args: T): U => { 15 | Log.time(name); 16 | const results = fn(...args); 17 | if (results instanceof Promise) { 18 | // @ts-ignore 19 | return new Promise((resolve, reject) => { 20 | results 21 | .then(results => { 22 | resolve(results); 23 | Log.timeEnd(name); 24 | }) 25 | .catch(error => { 26 | reject(error); 27 | Log.timeEnd(name); 28 | }); 29 | }); 30 | } else { 31 | Log.timeEnd(name); 32 | } 33 | return results; 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/utils/progress.ts: -------------------------------------------------------------------------------- 1 | import type { Progress } from 'got'; 2 | import ProgressBar from 'progress'; 3 | 4 | import Log from '../../log'; 5 | 6 | type ProgressTracker = (progress: Progress) => void; 7 | 8 | function createProgressTracker(_total?: number): ProgressTracker { 9 | let bar: ProgressBar | null = null; 10 | let transferredSoFar = 0; 11 | return (progress: Progress) => { 12 | if (!bar && (progress.total !== undefined || _total !== undefined)) { 13 | const total = (_total ?? progress.total) as number; 14 | bar = new ProgressBar('[:bar] :percent :etas', { 15 | complete: '=', 16 | incomplete: ' ', 17 | total, 18 | width: 64, 19 | }); 20 | Log.setBundleProgressBar(bar); 21 | } 22 | if (bar) { 23 | bar.tick(progress.transferred - transferredSoFar); 24 | } 25 | transferredSoFar = progress.transferred; 26 | }; 27 | } 28 | 29 | export { createProgressTracker }; 30 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/utils/promise.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns a promise that will be resolved after given ms milliseconds. 3 | * 4 | * @param ms A number of milliseconds to sleep. 5 | * @returns A promise that resolves after the provided number of milliseconds. 6 | */ 7 | async function sleep(ms: number): Promise { 8 | return new Promise(res => setTimeout(res, ms)); 9 | } 10 | 11 | export { sleep }; 12 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/utils/truncateLastLinesAsync.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import readLastLines from 'read-last-lines'; 3 | 4 | // truncate the last n lines in a file 5 | export async function truncateLastLinesAsync(filePath: string, n: number) { 6 | const [lines, { size }] = await Promise.all([ 7 | readLastLines.read(filePath, n), 8 | fs.promises.stat(filePath), 9 | ]); 10 | const toTruncate = lines.length; 11 | await fs.promises.truncate(filePath, size - toTruncate); 12 | } 13 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/webhooks/webhooksAddAsync.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | import { ora } from '../../utils/ora'; 4 | import { generateSecret, setupAsync, validateSecret, WebhookEvent } from './utils'; 5 | 6 | type Options = { 7 | url?: string; 8 | event?: WebhookEvent; 9 | secret?: string; 10 | }; 11 | 12 | export async function actionAsync(projectRoot: string, { url, event, ...options }: Options) { 13 | assert(typeof url === 'string' && /^https?/.test(url), '--url: a HTTP URL is required'); 14 | assert(typeof event === 'string', '--event: string is required'); 15 | const secret = validateSecret(options) || generateSecret(); 16 | 17 | const { experienceName, project, client } = await setupAsync(projectRoot); 18 | 19 | const spinner = ora(`Adding webhook to ${experienceName}`).start(); 20 | await client.postAsync(`projects/${project.id}/webhooks`, { url, event, secret }); 21 | spinner.succeed(); 22 | } 23 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/webhooks/webhooksAsync.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import CliTable from 'cli-table3'; 3 | 4 | import Log from '../../log'; 5 | import { setupAsync, WebhookEvent } from './utils'; 6 | 7 | type Webhook = { 8 | id: string; 9 | url: string; 10 | event: WebhookEvent; 11 | secret?: string; 12 | }; 13 | 14 | export async function actionAsync(projectRoot: string) { 15 | const { experienceName, project, client } = await setupAsync(projectRoot); 16 | 17 | const webhooks = await client.getAsync(`projects/${project.id}/webhooks`); 18 | if (webhooks.length) { 19 | const table = new CliTable({ head: ['Webhook ID', 'URL', 'Event'] }); 20 | table.push(...webhooks.map((hook: Webhook) => [hook.id, hook.url, hook.event])); 21 | Log.log(table.toString()); 22 | } else { 23 | Log.log(`${chalk.bold(experienceName)} has no webhooks.`); 24 | Log.log('Use `expo webhooks:add` to add one.'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/webhooks/webhooksRemoveAsync.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | import { setupAsync } from './utils'; 4 | 5 | type Options = { 6 | id?: string; 7 | }; 8 | 9 | export async function actionAsync(projectRoot: string, { id }: Options) { 10 | assert(typeof id === 'string', '--id must be a webhook ID'); 11 | const { project, client } = await setupAsync(projectRoot); 12 | 13 | await client.deleteAsync(`projects/${project.id}/webhooks/${id}`); 14 | } 15 | -------------------------------------------------------------------------------- /packages/expo-cli/src/commands/webhooks/webhooksUpdateAsync.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | import { ora } from '../../utils/ora'; 4 | import { setupAsync, validateSecret, WebhookEvent } from './utils'; 5 | 6 | type Options = { 7 | id?: string; 8 | url?: string; 9 | event?: WebhookEvent; 10 | secret?: string; 11 | }; 12 | 13 | export async function actionAsync(projectRoot: string, { id, url, event, ...options }: Options) { 14 | assert(typeof id === 'string', '--id must be a webhook ID'); 15 | assert(event == null || typeof event === 'string', '--event: string is required'); 16 | let secret = validateSecret(options); 17 | 18 | const { project, client } = await setupAsync(projectRoot); 19 | 20 | const webhook = await client.getAsync(`projects/${project.id}/webhooks/${id}`); 21 | event = event ?? webhook.event; 22 | secret = secret ?? webhook.secret; 23 | 24 | const spinner = ora(`Updating webhook ${id}`).start(); 25 | await client.patchAsync(`projects/${project.id}/webhooks/${id}`, { url, event, secret }); 26 | spinner.succeed(); 27 | } 28 | -------------------------------------------------------------------------------- /packages/expo-cli/src/credentials/__tests__/fixtures/mocks-context.ts: -------------------------------------------------------------------------------- 1 | import merge from 'lodash/merge'; 2 | 3 | import { getAndroidApiMock } from './mocks-android'; 4 | import { testAppJson, testUsername } from './mocks-constants'; 5 | import { appleCtxMock, getIosApiMock } from './mocks-ios'; 6 | 7 | export function getCtxMock(mockOverride: { [key: string]: any } = {}) { 8 | const defaultMock = { 9 | ios: getIosApiMock(), 10 | android: getAndroidApiMock(), 11 | appleCtx: appleCtxMock, 12 | ensureAppleCtx: jest.fn(), 13 | user: { 14 | username: testUsername, 15 | }, 16 | hasAppleCtx: jest.fn(() => true), 17 | hasProjectContext: true, 18 | manifest: testAppJson, 19 | projectDir: '.', 20 | }; 21 | return merge(defaultMock, mockOverride); 22 | } 23 | -------------------------------------------------------------------------------- /packages/expo-cli/src/credentials/index.ts: -------------------------------------------------------------------------------- 1 | export { Context } from './context'; 2 | 3 | export { runCredentialsManagerStandalone } from './route'; 4 | -------------------------------------------------------------------------------- /packages/expo-cli/src/credentials/utils/__mocks__/git.ts: -------------------------------------------------------------------------------- 1 | export const gitStatusAsync = jest.fn(); 2 | -------------------------------------------------------------------------------- /packages/expo-cli/src/credentials/utils/git.ts: -------------------------------------------------------------------------------- 1 | import spawnAsync from '@expo/spawn-async'; 2 | 3 | interface GitStatusOptions { 4 | showUntracked?: boolean; 5 | } 6 | 7 | export async function gitStatusAsync({ showUntracked }: GitStatusOptions = {}): Promise { 8 | return (await spawnAsync('git', ['status', '-s', showUntracked ? '-uall' : '-uno'])).stdout; 9 | } 10 | -------------------------------------------------------------------------------- /packages/expo-cli/src/credentials/utils/provisioningProfile.ts: -------------------------------------------------------------------------------- 1 | import plist, { PlistArray, PlistObject } from '@expo/plist'; 2 | 3 | interface AppleTeam { 4 | teamId: string; 5 | teamName: string; 6 | } 7 | 8 | function readAppleTeam(dataBase64: string): AppleTeam { 9 | const profilePlist = parse(dataBase64); 10 | const teamId = (profilePlist['TeamIdentifier'] as PlistArray)?.[0] as string; 11 | const teamName = profilePlist['TeamName'] as string; 12 | if (!teamId) { 13 | throw new Error('Team identifier is missing from provisoning profile'); 14 | } 15 | return { teamId, teamName }; 16 | } 17 | 18 | function readProfileName(dataBase64: string): string { 19 | const profilePlist = parse(dataBase64); 20 | return profilePlist['Name'] as string; 21 | } 22 | 23 | function parse(dataBase64: string): PlistObject { 24 | try { 25 | const buffer = Buffer.from(dataBase64, 'base64'); 26 | const profile = buffer.toString('utf-8'); 27 | return plist.parse(profile) as PlistObject; 28 | } catch { 29 | throw new Error('Provisioning profile is malformed'); 30 | } 31 | } 32 | 33 | export { readAppleTeam, readProfileName }; 34 | -------------------------------------------------------------------------------- /packages/expo-cli/src/credentials/views/AndroidPushCredentials.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import CommandError from '../../CommandError'; 4 | import Log from '../../log'; 5 | import prompt from '../../utils/prompts'; 6 | import { Context, IView } from '../context'; 7 | 8 | export class UpdateFcmKey implements IView { 9 | constructor(private experienceName: string) {} 10 | 11 | async open(ctx: Context): Promise { 12 | if (ctx.nonInteractive) { 13 | throw new CommandError( 14 | 'NON_INTERACTIVE', 15 | "Start the CLI without the '--non-interactive' flag to update the FCM Api key." 16 | ); 17 | } 18 | 19 | const { fcmApiKey } = await prompt({ 20 | type: 'text', 21 | name: 'fcmApiKey', 22 | message: 'FCM Api Key', 23 | validate: (value: string) => value.length > 0 || "FCM Api Key can't be empty", 24 | }); 25 | 26 | await ctx.android.updateFcmKey(this.experienceName, fcmApiKey); 27 | Log.log(chalk.green('Updated successfully')); 28 | return null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/expo-cli/src/credentials/views/IosAppCredentials.ts: -------------------------------------------------------------------------------- 1 | import { Context, IView } from '../context'; 2 | 3 | export class CreateAppCredentialsIos implements IView { 4 | async open(context: Context): Promise { 5 | throw new Error('no implemented'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/expo-cli/src/credentials/views/SetupIosDist.ts: -------------------------------------------------------------------------------- 1 | import { AppLookupParams } from '../api/IosApi'; 2 | import { Context, IView } from '../context'; 3 | import * as iosDistView from './IosDistCert'; 4 | 5 | export class SetupIosDist implements IView { 6 | constructor(private app: AppLookupParams) {} 7 | 8 | async open(ctx: Context): Promise { 9 | if (!ctx.user) { 10 | throw new Error(`This workflow requires you to be logged in.`); 11 | } 12 | 13 | const configuredDistCert = await ctx.ios.getDistCert(this.app); 14 | 15 | if (configuredDistCert) { 16 | // we dont need to setup if we have a valid dist cert on file 17 | const isValid = await iosDistView.validateDistributionCertificate(ctx, configuredDistCert); 18 | if (isValid) { 19 | return null; 20 | } 21 | } 22 | 23 | return new iosDistView.CreateOrReuseDistributionCert(this.app); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/expo-cli/src/utils/__mocks__/prompts.ts: -------------------------------------------------------------------------------- 1 | import { Choice, Options, PromptObject as Question } from 'prompts'; 2 | 3 | type PromptOptions = { nonInteractiveHelp?: string } & Options; 4 | 5 | type CliQuestions = Question | Question[]; 6 | 7 | const prompt = jest.fn( 8 | (questions: CliQuestions, { nonInteractiveHelp }: { nonInteractiveHelp?: string } = {}) => { 9 | return {}; 10 | } 11 | ); 12 | 13 | // todo: replace this workaround, its still selectable by the cursor 14 | // see: https://github.com/terkelg/prompts/issues/254 15 | prompt.separator = (title: string): Choice => ({ title, disable: true, value: undefined }); 16 | 17 | export const selectAsync = jest.fn( 18 | (questions: any, options?: PromptOptions) => prompt(questions, options).value 19 | ); 20 | 21 | export const confirmAsync = jest.fn( 22 | (questions: Question | Question[], options?: PromptOptions) => prompt(questions, options).value 23 | ); 24 | 25 | export default prompt; 26 | -------------------------------------------------------------------------------- /packages/expo-cli/src/utils/__tests__/getRemoteVersionsForSdk.test.ts: -------------------------------------------------------------------------------- 1 | import { getRemoteVersionsForSdk } from '../getRemoteVersionsForSdk'; 2 | 3 | describe(getRemoteVersionsForSdk, () => { 4 | it(`returns results for a valid SDK version`, async () => { 5 | const data = await getRemoteVersionsForSdk('43.0.0'); 6 | expect(data).toBeDefined(); 7 | expect(Object.keys(data).length).toBeGreaterThan(0); 8 | expect(typeof data['react-native']).toBe('string'); 9 | expect(data.react).toEqual(data['react-dom']); 10 | }); 11 | 12 | it(`returns an empty object for invalid SDK version`, async () => { 13 | const data = await getRemoteVersionsForSdk('Expo'); 14 | expect(data).toBeDefined(); 15 | expect(Object.keys(data).length).toBe(0); 16 | }); 17 | 18 | it(`returns an empty object for unspecified SDK version`, async () => { 19 | const data = await getRemoteVersionsForSdk(undefined); 20 | expect(data).toBeDefined(); 21 | expect(Object.keys(data).length).toBe(0); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/expo-cli/src/utils/getRemoteVersionsForSdk.ts: -------------------------------------------------------------------------------- 1 | import { Versions } from 'xdl'; 2 | 3 | export type DependencyList = Record; 4 | 5 | export const getRemoteVersionsForSdk = async (sdkVersion?: string): Promise => { 6 | const { sdkVersions } = await Versions.versionsAsync({ skipCache: true }); 7 | if (sdkVersion && sdkVersion in sdkVersions) { 8 | const { relatedPackages, facebookReactVersion, facebookReactNativeVersion } = sdkVersions[ 9 | sdkVersion 10 | ]; 11 | const reactVersion = facebookReactVersion 12 | ? { 13 | react: facebookReactVersion, 14 | 'react-dom': facebookReactVersion, 15 | } 16 | : undefined; 17 | return { 18 | ...relatedPackages, 19 | ...reactVersion, 20 | 'react-native': facebookReactNativeVersion, 21 | }; 22 | } 23 | return {}; 24 | }; 25 | -------------------------------------------------------------------------------- /packages/expo-cli/src/utils/matchFileNameOrURLFromStackTrace.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Given a line from a metro stack trace, this can attempt to extract 3 | * the file name or URL, omitting the code location. 4 | * Can be used to filter files from the stacktrace like LogBox. 5 | * 6 | * @param traceLine 7 | */ 8 | export function matchFileNameOrURLFromStackTrace(traceMessage: string): string | null { 9 | if (!traceMessage.includes(' in ')) return null; 10 | const traceLine = traceMessage.split(' in ')[0]?.trim(); 11 | // Is URL 12 | // "http://127.0.0.1:19000/index.bundle?platform=ios&dev=true&hot=false&minify=false:110910:3 in global code" 13 | if (traceLine.match(/https?:\/\//g)) { 14 | const [url, params] = traceLine.split('?'); 15 | 16 | const results: string[] = [url]; 17 | if (params) { 18 | const paramsWithoutLocation = params.replace(/:(\d+)/g, '').trim(); 19 | results.push(paramsWithoutLocation); 20 | } 21 | return results.filter(Boolean).join('?'); 22 | } 23 | 24 | // "node_modules/react-native/Libraries/LogBox/LogBox.js:117:10 in registerWarning" 25 | // "somn.js:1:0 in " 26 | return traceLine.replace(/:(\d+)/g, '').trim(); 27 | } 28 | -------------------------------------------------------------------------------- /packages/expo-cli/src/utils/migration.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import Log from '../log'; 4 | 5 | // Not needed anymore, we tell people to migrate on every command 6 | export function warnAboutLocalCLI(projectRoot: string, { localCmd }: { localCmd: string }) { 7 | // const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); 8 | // const useLocalCLI = boolish('EXPO_USE_LOCAL_CLI', true); 9 | // if (Versions.gteSdkVersion(exp, '46.0.0') && useLocalCLI) { 10 | // Log.warn( 11 | // chalk`\nThis command is being executed with the global Expo CLI. ${learnMore( 12 | // 'https://blog.expo.dev/the-new-expo-cli-f4250d8e3421' 13 | // )}\nTo use the local CLI instead (recommended in SDK 46 and higher), run:\n\u203A {bold npx expo ${localCmd}}\n` 14 | // ); 15 | // } 16 | } 17 | 18 | export function warnMigration(toCommand: string) { 19 | Log.warn(chalk`\nMigrate to using:\n\u203A {bold ${toCommand}}\n`); 20 | } 21 | -------------------------------------------------------------------------------- /packages/expo-cli/src/utils/update.ts: -------------------------------------------------------------------------------- 1 | import { ModuleVersion } from 'xdl'; 2 | 3 | const packageJSON = require('../../package.json'); 4 | 5 | const ModuleVersionChecker = ModuleVersion.createModuleVersionChecker( 6 | packageJSON.name, 7 | packageJSON.version 8 | ); 9 | 10 | async function checkForUpdateAsync() { 11 | return await ModuleVersionChecker.checkAsync(); 12 | } 13 | 14 | export default { 15 | checkForUpdateAsync, 16 | }; 17 | -------------------------------------------------------------------------------- /packages/expo-cli/src/utils/validators.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | function nonEmptyInput(val: string) { 4 | return val !== ''; 5 | } 6 | 7 | // note(cedric): export prompts-compatible validators, 8 | // refactor when prompt is replaced with prompts 9 | const promptsNonEmptyInput = nonEmptyInput; 10 | const promptsExistingFile = async (filePath: string) => { 11 | try { 12 | const stats = await fs.promises.stat(filePath); 13 | if (stats.isFile()) { 14 | return true; 15 | } 16 | return 'Input is not a file.'; 17 | } catch { 18 | return 'File does not exist.'; 19 | } 20 | }; 21 | 22 | export { nonEmptyInput, promptsNonEmptyInput, promptsExistingFile }; 23 | -------------------------------------------------------------------------------- /packages/expo-cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base", 3 | "compilerOptions": { 4 | "outDir": "build", 5 | "rootDir": "src", 6 | }, 7 | "include": ["src"], 8 | "exclude": ["**/__mocks__/*", "**/__tests__/*"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/expo-codemod/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['universe/node', 'universe/web'], 3 | rules: { 4 | 'react/jsx-fragments': 'off', 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/expo-codemod/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present 650 Industries, Inc. (aka Expo) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/expo-codemod/bin/expo-codemod.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../build/cli.js') 4 | .runAsync(process.argv) 5 | .catch(error => { 6 | console.error(error); 7 | process.exit(1); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/expo-codemod/jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | preset: '../../jest/unit-test-config', 5 | rootDir: path.resolve(__dirname), 6 | displayName: require('./package').name, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/expo-codemod/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "expo-codemod", 3 | "version": "1.1.6", 4 | "description": "Codemod scripts for Expo apps", 5 | "author": "Expo", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/expo/expo-cli.git", 10 | "directory": "packages/expo-codemod" 11 | }, 12 | "bin": { 13 | "expo-codemod": "./bin/expo-codemod.js" 14 | }, 15 | "scripts": { 16 | "watch": "tsc --watch --preserveWatchOutput", 17 | "build": "tsc", 18 | "prepare": "yarn run clean && yarn build", 19 | "clean": "rimraf build ./tsconfig.tsbuildinfo", 20 | "test": "jest", 21 | "test:watch": "jest --watch" 22 | }, 23 | "files": [ 24 | "bin", 25 | "build" 26 | ], 27 | "dependencies": { 28 | "@expo/spawn-async": "^1.5.0", 29 | "camelcase": "5.3.1", 30 | "chalk": "^4.0.0", 31 | "globby": "^11.0.0", 32 | "jscodeshift": "0.11.0", 33 | "multimatch": "^4.0.0", 34 | "yargs-parser": "^13.1.0" 35 | }, 36 | "devDependencies": { 37 | "@types/jscodeshift": "^0.11.0", 38 | "@types/multimatch": "^2.1.3", 39 | "@types/yargs-parser": "^13.0.0", 40 | "ast-types": "^0.14.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/expo-codemod/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base", 3 | "include": ["src/**/*.ts"], 4 | "compilerOptions": { 5 | "outDir": "build", 6 | "rootDir": "src" 7 | }, 8 | "exclude": ["**/__mocks__/*", "**/__tests__/*"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/expo-doctor/README.md: -------------------------------------------------------------------------------- 1 | # `expo-doctor` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/expo-doctor). 4 | -------------------------------------------------------------------------------- /packages/expo-env-info/README.md: -------------------------------------------------------------------------------- 1 | # `expo-env-info` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/expo-env-info). 4 | -------------------------------------------------------------------------------- /packages/expo-optimize/README.md: -------------------------------------------------------------------------------- 1 | # expo-optimize 2 | 3 | This package has been deprecated. There are many tools which specialize in optimizing images for hosting on the web. We recommend using [Squoosh](https://squoosh.app/) or [imagemin](https://github.com/imagemin/imagemin-cli). 4 | 5 | To save on hosting costs, you should always optimize your images and other assets. Be sure to use optimal assets before deploying to [EAS Update](https://docs.expo.dev/eas-update/). 6 | -------------------------------------------------------------------------------- /packages/image-utils/README.md: -------------------------------------------------------------------------------- 1 | # `@expo/image-utils` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/%40expo/image-utils). 4 | -------------------------------------------------------------------------------- /packages/install-expo-modules/README.md: -------------------------------------------------------------------------------- 1 | # `install-expo-modules` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/install-expo-modules). 4 | -------------------------------------------------------------------------------- /packages/json-file/README.md: -------------------------------------------------------------------------------- 1 | # `@expo/json-file` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/%40expo/json-file). 4 | -------------------------------------------------------------------------------- /packages/metro-config/README.md: -------------------------------------------------------------------------------- 1 | # `@expo/metro-config` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/%40expo/metro-config). -------------------------------------------------------------------------------- /packages/next-adapter/README.md: -------------------------------------------------------------------------------- 1 | # `@expo/next-adapter` 2 | 3 | This package has [moved to the `expo/expo-webpack-integrations` repo](https://github.com/expo/expo-webpack-integrations/tree/main/packages/next-adapter#readme). 4 | -------------------------------------------------------------------------------- /packages/osascript/README.md: -------------------------------------------------------------------------------- 1 | # `@expo/osascript` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/%40expo/osascript). 4 | -------------------------------------------------------------------------------- /packages/package-manager/README.md: -------------------------------------------------------------------------------- 1 | # `@expo/package-manager` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/%40expo/package-manager). 4 | -------------------------------------------------------------------------------- /packages/pkcs12/README.md: -------------------------------------------------------------------------------- 1 | # `@expo/pkcs12` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/%40expo/pkcs12). 4 | -------------------------------------------------------------------------------- /packages/plist/README.md: -------------------------------------------------------------------------------- 1 | # `@expo/plist` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/%40expo/plist). 4 | -------------------------------------------------------------------------------- /packages/pod-install/README.md: -------------------------------------------------------------------------------- 1 | # `pod-install` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/pod-install). 4 | -------------------------------------------------------------------------------- /packages/prebuild-config/README.md: -------------------------------------------------------------------------------- 1 | # `@expo/prebuild-config` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/%40expo/prebuild-config). 4 | -------------------------------------------------------------------------------- /packages/pwa/README.md: -------------------------------------------------------------------------------- 1 | # `expo-pwa` 2 | 3 | This package has [moved to the `expo/expo-webpack-integrations` repo](https://github.com/expo/expo-webpack-integrations/tree/main/packages/pwa#readme). 4 | -------------------------------------------------------------------------------- /packages/schemer/README.md: -------------------------------------------------------------------------------- 1 | # `@expo/schemer` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/%40expo/schemer). 4 | -------------------------------------------------------------------------------- /packages/traveling-fastlane/README.md: -------------------------------------------------------------------------------- 1 | # traveling fastlane 2 | 3 | This package suite has been deprecated in favor of EAS Build. 4 | -------------------------------------------------------------------------------- /packages/uri-scheme/README.md: -------------------------------------------------------------------------------- 1 | # `uri-scheme` 2 | 3 | This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/uri-scheme). 4 | -------------------------------------------------------------------------------- /packages/webpack-config/README.md: -------------------------------------------------------------------------------- 1 | # `@expo/webpack-config` 2 | 3 | This package has [moved to the `expo/expo-webpack-integrations` repo](https://github.com/expo/expo-webpack-integrations/tree/main/packages/webpack-config#readme). 4 | -------------------------------------------------------------------------------- /packages/xdl/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present 650 Industries, Inc. (aka Expo) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/xdl/LICENSE-third-party: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Drifty Co 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/xdl/__mocks__/@expo/image-utils.ts: -------------------------------------------------------------------------------- 1 | export async function generateImageAsync(input: any, { src }) { 2 | const fs = require('fs'); 3 | return { source: fs.readFileSync(src) }; 4 | } 5 | 6 | export async function compositeImagesAsync({ foreground }) { 7 | return foreground; 8 | } 9 | -------------------------------------------------------------------------------- /packages/xdl/__mocks__/@expo/rudder-sdk-node.ts: -------------------------------------------------------------------------------- 1 | export default jest.fn().mockImplementation(() => ({ 2 | logger: jest.fn(), 3 | identify: jest.fn(), 4 | track: jest.fn(), 5 | })); 6 | -------------------------------------------------------------------------------- /packages/xdl/__mocks__/analytics-node.ts: -------------------------------------------------------------------------------- 1 | class Analytics { 2 | identify = jest.fn(); 3 | group = jest.fn(); 4 | track = jest.fn(); 5 | page = jest.fn(); 6 | alias = jest.fn(); 7 | flush = jest.fn(); 8 | enqueue = jest.fn(); 9 | } 10 | 11 | module.exports = Analytics; 12 | -------------------------------------------------------------------------------- /packages/xdl/__mocks__/dtrace-provider.ts: -------------------------------------------------------------------------------- 1 | module.exports = null; 2 | -------------------------------------------------------------------------------- /packages/xdl/__mocks__/fs.ts: -------------------------------------------------------------------------------- 1 | import { fs } from 'memfs'; 2 | module.exports = fs; 3 | -------------------------------------------------------------------------------- /packages/xdl/__mocks__/os.ts: -------------------------------------------------------------------------------- 1 | const os = jest.requireActual('os'); 2 | 3 | os.homedir = jest.fn(() => '/home'); 4 | 5 | module.exports = os; 6 | -------------------------------------------------------------------------------- /packages/xdl/__mocks__/resolve-from.ts: -------------------------------------------------------------------------------- 1 | const resolveFrom = require(require.resolve('resolve-from')); 2 | 3 | const silent = (fromDirectory, request) => { 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | try { 7 | fromDirectory = fs.realpathSync(fromDirectory); 8 | } catch (error: any) { 9 | if (error.code === 'ENOENT') { 10 | fromDirectory = path.resolve(fromDirectory); 11 | } else { 12 | return; 13 | } 14 | } 15 | 16 | let outputPath = path.join(fromDirectory, 'node_modules', request); 17 | if (fs.existsSync(outputPath)) { 18 | return outputPath; 19 | } 20 | if (!path.extname(outputPath)) { 21 | outputPath += '.js'; 22 | } 23 | if (fs.existsSync(outputPath)) { 24 | return outputPath; 25 | } 26 | }; 27 | 28 | module.exports = (fromDirectory, request) => { 29 | const path = silent(fromDirectory, request); 30 | if (!path) { 31 | return resolveFrom(fromDirectory, request); 32 | } 33 | return path; 34 | }; 35 | 36 | module.exports.silent = silent; 37 | -------------------------------------------------------------------------------- /packages/xdl/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ['@expo/babel-preset-cli'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/xdl/binaries/linux/adb/adb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/xdl/binaries/linux/adb/adb -------------------------------------------------------------------------------- /packages/xdl/binaries/linux/get-path-bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ~/.expo/bashrc > /dev/null 4 | 5 | echo $PATH 6 | -------------------------------------------------------------------------------- /packages/xdl/binaries/osx/adb/adb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/xdl/binaries/osx/adb/adb -------------------------------------------------------------------------------- /packages/xdl/binaries/osx/get-path-bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ~/.expo/bashrc > /dev/null 4 | 5 | echo $PATH 6 | -------------------------------------------------------------------------------- /packages/xdl/binaries/osx/watchman/watchman: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/xdl/binaries/osx/watchman/watchman -------------------------------------------------------------------------------- /packages/xdl/binaries/windows/adb/AdbWinApi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/xdl/binaries/windows/adb/AdbWinApi.dll -------------------------------------------------------------------------------- /packages/xdl/binaries/windows/adb/AdbWinUsbApi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/xdl/binaries/windows/adb/AdbWinUsbApi.dll -------------------------------------------------------------------------------- /packages/xdl/binaries/windows/adb/adb.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/xdl/binaries/windows/adb/adb.exe -------------------------------------------------------------------------------- /packages/xdl/jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | preset: '../../jest/unit-test-config', 5 | rootDir: path.resolve(__dirname), 6 | displayName: require('./package').name, 7 | roots: ['__mocks__', 'src'], 8 | setupFiles: ['/jest/fs-mock-setup.ts'], 9 | testRunner: 'jest-jasmine2', 10 | }; 11 | -------------------------------------------------------------------------------- /packages/xdl/jest/fs-mock-setup.ts: -------------------------------------------------------------------------------- 1 | jest.mock('@expo/image-utils'); 2 | jest.mock('@expo/rudder-sdk-node'); 3 | jest.mock('dtrace-provider'); 4 | jest.mock('fs'); 5 | jest.mock('os'); 6 | jest.mock('resolve-from'); 7 | -------------------------------------------------------------------------------- /packages/xdl/jest/integration-test-config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | preset: '../../jest/unit-test-config', 5 | rootDir: path.resolve(__dirname, '..'), 6 | displayName: require('../package.json').name, 7 | roots: ['src'], 8 | testTimeout: 20000, 9 | testRegex: '__integration_tests__/.*(test|spec)\\.[jt]sx?$', 10 | }; 11 | -------------------------------------------------------------------------------- /packages/xdl/scripts/updateCaches.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const { writeJsonSync } = require('fs-extra'); 3 | const path = require('path'); 4 | 5 | axios 6 | .get('https://exp.host/--/versions') 7 | .then(async ({ data }) => { 8 | writeJsonSync(path.join(__dirname, '../caches/versions.json'), data); 9 | 10 | for (const version of Object.keys(data.sdkVersions)) { 11 | if (data.sdkVersions[version].isDeprecated) { 12 | continue; 13 | } 14 | const { 15 | data: { data: schema }, 16 | } = await axios.get(`https://exp.host/--/api/v2/project/configuration/schema/${version}`); 17 | 18 | const filePath = path.join(__dirname, `../caches/schema-${version}.json`); 19 | console.log('Writing', filePath); 20 | writeJsonSync(filePath, schema); 21 | } 22 | }) 23 | .then(() => console.log('Caches updated.')) 24 | .catch(error => { 25 | console.error(error); 26 | console.error('Updating caches failed.'); 27 | process.exit(1); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/xdl/src/Config.ts: -------------------------------------------------------------------------------- 1 | import getenv from 'getenv'; 2 | 3 | import { Env } from './internal'; 4 | 5 | interface ApiConfig { 6 | scheme: string; 7 | host: string; 8 | port: number | null; 9 | } 10 | 11 | interface XDLConfig { 12 | api: ApiConfig; 13 | developerTool: string; 14 | } 15 | 16 | function getAPI(): ApiConfig { 17 | if (Env.isLocal()) { 18 | return { 19 | scheme: 'http', 20 | host: 'localhost', 21 | port: 3000, 22 | }; 23 | } else if (Env.isStaging()) { 24 | return { 25 | scheme: getenv.string('XDL_SCHEME', 'https'), 26 | host: 'staging.exp.host', 27 | port: getenv.int('XDL_PORT', 0) || null, 28 | }; 29 | } else { 30 | return { 31 | scheme: getenv.string('XDL_SCHEME', 'https'), 32 | host: getenv.string('XDL_HOST', 'exp.host'), 33 | port: getenv.int('XDL_PORT', 0) || null, 34 | }; 35 | } 36 | } 37 | 38 | const config: XDLConfig = { 39 | api: getAPI(), 40 | developerTool: 'expo-cli', 41 | }; 42 | 43 | export default config; 44 | -------------------------------------------------------------------------------- /packages/xdl/src/ConnectionStatus.ts: -------------------------------------------------------------------------------- 1 | let offline: boolean = false; 2 | 3 | export function setIsOffline(bool: boolean): void { 4 | offline = bool; 5 | } 6 | 7 | export function isOffline(): boolean { 8 | return offline; 9 | } 10 | -------------------------------------------------------------------------------- /packages/xdl/src/ErrorCode.ts: -------------------------------------------------------------------------------- 1 | export type ErrorCode = 2 | // Auth Errors 3 | | 'INVALID_USERNAME_PASSWORD' 4 | | 'TOO_MANY_ATTEMPTS' 5 | | 'REGISTRATION_ERROR' 6 | | 'LEGACY_ACCOUNT_ERROR' 7 | | 'ROBOT_ACCOUNT_ERROR' 8 | | 'USER_ACCOUNT_ERROR' 9 | | 'DIRECTORY_ALREADY_EXISTS' 10 | | 'HOOK_INITIALIZATION_ERROR' 11 | | 'INVALID_ARGUMENT' 12 | | 'INVALID_ASSETS' 13 | | 'INVALID_BUNDLE' 14 | | 'INVALID_JSON' 15 | | 'INVALID_MANIFEST' 16 | | 'INVALID_OPTIONS' 17 | | 'INVALID_VERSION' 18 | | 'NGROK_ERROR' 19 | | 'NO_EXPO_SERVER_PORT' 20 | | 'NO_PACKAGE_JSON' 21 | | 'NO_PACKAGER_PORT' 22 | | 'NO_PORT_FOUND' 23 | | 'NO_PROJECT_ROOT' 24 | | 'NO_SDK_VERSION' 25 | | 'NOT_LOGGED_IN' 26 | | 'PLATFORM_NOT_SUPPORTED' 27 | | 'PUBLISH_VALIDATION_ERROR' 28 | | 'WONT_OVERWRITE_WITHOUT_FORCE' 29 | | 'XCODE_LICENSE_NOT_ACCEPTED' 30 | | 'APP_NOT_INSTALLED' 31 | | 'SIMCTL_NOT_AVAILABLE' 32 | | 'WEB_NOT_CONFIGURED' 33 | | 'WEBPACK_BUNDLE' 34 | | 'WEBPACK_DEPRECATED' 35 | | 'WEBPACK_INVALID_OPTION' 36 | | 'NETWORK_REQUIRED' 37 | | 'ROBOT_OWNER_ERROR' 38 | // Shell Apps 39 | | 'CREDENTIAL_ERROR' 40 | // Development build 41 | | 'NO_DEV_CLIENT_SCHEME'; 42 | -------------------------------------------------------------------------------- /packages/xdl/src/Exp.ts: -------------------------------------------------------------------------------- 1 | import { ApiV2, UserManager } from './internal'; 2 | 3 | export async function sendAsync(recipient: string, url_: string, allowUnauthed: boolean = true) { 4 | const user = await UserManager.ensureLoggedInAsync(); 5 | const api = ApiV2.clientForUser(user); 6 | return await api.postAsync('send-project', { 7 | emailOrPhone: recipient, 8 | url: url_, 9 | includeExpoLinks: allowUnauthed, 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /packages/xdl/src/Extract.ts: -------------------------------------------------------------------------------- 1 | import spawnAsync from '@expo/spawn-async'; 2 | import tar from 'tar'; 3 | 4 | export async function extractAsync(archive: string, dir: string): Promise { 5 | try { 6 | if (process.platform !== 'win32') { 7 | await spawnAsync('tar', ['-xf', archive, '-C', dir], { 8 | stdio: 'inherit', 9 | cwd: __dirname, 10 | }); 11 | return; 12 | } 13 | } catch (e: any) { 14 | console.error(e.message); 15 | } 16 | // tar node module has previously had problems with big files, and seems to 17 | // be slower, so only use it as a backup. 18 | await tar.extract({ file: archive, cwd: dir }); 19 | } 20 | -------------------------------------------------------------------------------- /packages/xdl/src/LoadingEvent.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | START_LOADING: 'START_LOADING', 3 | STOP_LOADING: 'STOP_LOADING', 4 | START_PROGRESS_BAR: 'START_PROGRESS_BAR', 5 | TICK_PROGRESS_BAR: 'TICK_PROGRESS_BAR', 6 | STOP_PROGRESS_BAR: 'STOP_PROGRESS_BAR', 7 | }; 8 | -------------------------------------------------------------------------------- /packages/xdl/src/Project.ts: -------------------------------------------------------------------------------- 1 | import { 2 | startExpoServerAsync, 3 | StartOptions, 4 | startReactNativeServerAsync, 5 | startTunnelsAsync, 6 | stopReactNativeServerAsync, 7 | stopTunnelsAsync, 8 | } from './internal'; 9 | 10 | export { 11 | startTunnelsAsync, 12 | stopTunnelsAsync, 13 | startExpoServerAsync, 14 | StartOptions, 15 | startReactNativeServerAsync, 16 | stopReactNativeServerAsync, 17 | }; 18 | export { 19 | broadcastMessage, 20 | createBundlesAsync, 21 | getPublishExpConfigAsync, 22 | prepareHooks, 23 | publishAsync, 24 | PublishedProjectResult, 25 | PublishOptions, 26 | runHook, 27 | startAsync, 28 | stopAsync, 29 | startWebpackAsync, 30 | writeArtifactSafelyAsync, 31 | LoadedHook, 32 | } from './internal'; 33 | -------------------------------------------------------------------------------- /packages/xdl/src/Session.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from 'uuid'; 2 | 3 | import { UserSettings } from './internal'; 4 | 5 | function _newIdentifier(type = 'c') { 6 | const bytes = uuidv4(null, Buffer.alloc(16)); 7 | const base64 = bytes.toString('base64'); 8 | const slug = base64 9 | // Replace + with - (see RFC 4648, sec. 5) 10 | .replace(/\+/g, '-') 11 | // Replace / with _ (see RFC 4648, sec. 5) 12 | .replace(/\//g, '_') 13 | // Drop '==' padding 14 | .substring(0, 22); 15 | return type + '_' + slug; 16 | } 17 | 18 | export async function clientIdAsync(): Promise { 19 | let clientId = await UserSettings.getAsync('accessToken', null); 20 | if (clientId === null) { 21 | clientId = _newIdentifier(); 22 | await setClientIdAsync(clientId); 23 | } 24 | return clientId; 25 | } 26 | 27 | export async function setClientIdAsync(token: string) { 28 | await UserSettings.setAsync('accessToken', token); 29 | return token; 30 | } 31 | -------------------------------------------------------------------------------- /packages/xdl/src/UnifiedAnalytics.ts: -------------------------------------------------------------------------------- 1 | import { AnalyticsClient } from './internal'; 2 | 3 | const client = new AnalyticsClient(); 4 | 5 | export default client; 6 | -------------------------------------------------------------------------------- /packages/xdl/src/XDLError.ts: -------------------------------------------------------------------------------- 1 | import { ErrorCode } from './internal'; 2 | 3 | const ERROR_PREFIX = 'Error: '; 4 | 5 | export default class XDLError extends Error { 6 | readonly name = 'XDLError'; 7 | 8 | code: string; 9 | isXDLError: true; 10 | 11 | constructor(code: ErrorCode, message: string) { 12 | super(''); 13 | 14 | // If e.toString() was called to get `message` we don't want it to look 15 | // like "Error: Error:". 16 | if (message.startsWith(ERROR_PREFIX)) { 17 | message = message.substring(ERROR_PREFIX.length); 18 | } 19 | 20 | this.message = message; 21 | this.code = code; 22 | this.isXDLError = true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/xdl/src/__tests__/Env-test.ts: -------------------------------------------------------------------------------- 1 | import { shouldUseDevServer } from '../Env'; 2 | 3 | describe(shouldUseDevServer, () => { 4 | beforeEach(() => { 5 | delete process.env.EXPO_USE_DEV_SERVER; 6 | }); 7 | it(`defaults to true for projects without an SDK version`, () => { 8 | expect(shouldUseDevServer({ sdkVersion: 'UNVERSIONED' })).toBe(true); 9 | expect(shouldUseDevServer({ sdkVersion: undefined })).toBe(true); 10 | expect(shouldUseDevServer({})).toBe(true); 11 | }); 12 | it(`is true for projects in SDK +40`, () => { 13 | expect(shouldUseDevServer({ sdkVersion: '40.0.0' })).toBe(true); 14 | expect(shouldUseDevServer({ sdkVersion: '41.0.0' })).toBe(true); 15 | }); 16 | it(`is false for projects in SDK 39`, () => { 17 | expect(shouldUseDevServer({ sdkVersion: '39.0.0' })).toBe(false); 18 | expect(shouldUseDevServer({ sdkVersion: '10.0.0' })).toBe(false); 19 | }); 20 | it(`uses the env variable`, () => { 21 | process.env.EXPO_USE_DEV_SERVER = String(true); 22 | expect(shouldUseDevServer({ sdkVersion: '10.0.0' })).toBe(true); 23 | expect(shouldUseDevServer({ sdkVersion: '41.0.0' })).toBe(true); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/xdl/src/__tests__/UserSettings-test.ts: -------------------------------------------------------------------------------- 1 | import UserSettings from '../UserSettings'; 2 | 3 | describe(UserSettings.dotExpoHomeDirectory, () => { 4 | beforeEach(() => { 5 | delete process.env.__UNSAFE_EXPO_HOME_DIRECTORY; 6 | delete process.env.EXPO_STAGING; 7 | delete process.env.EXPO_LOCAL; 8 | }); 9 | 10 | it(`gets the default state directory`, () => { 11 | expect(UserSettings.dotExpoHomeDirectory()).toBe('/home/.expo'); 12 | }); 13 | it(`gets the staging state directory`, () => { 14 | process.env.EXPO_STAGING = 'true'; 15 | expect(UserSettings.dotExpoHomeDirectory()).toBe('/home/.expo-staging'); 16 | }); 17 | it(`gets the local state directory`, () => { 18 | process.env.EXPO_LOCAL = 'true'; 19 | expect(UserSettings.dotExpoHomeDirectory()).toBe('/home/.expo-local'); 20 | }); 21 | it(`gets the custom state directory`, () => { 22 | process.env.__UNSAFE_EXPO_HOME_DIRECTORY = '/foobar/yolo'; 23 | expect(UserSettings.dotExpoHomeDirectory()).toBe('/foobar/yolo'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/xdl/src/__tests__/fixtures/publish-test-app/App.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import React from 'react'; 3 | import { Text, View } from 'react-native'; 4 | 5 | export default () => ( 6 | 7 | Hello, world! 8 | 9 | ); 10 | -------------------------------------------------------------------------------- /packages/xdl/src/__tests__/fixtures/publish-test-app/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "privacy": "unlisted", 4 | "sdkVersion": "37.0.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/xdl/src/__tests__/fixtures/publish-test-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": { 4 | "expo": "~37.0.3", 5 | "react": "~16.9.0", 6 | "react-native": "https://github.com/expo/react-native/archive/sdk-37.0.1.tar.gz" 7 | } 8 | } -------------------------------------------------------------------------------- /packages/xdl/src/apple/native-run/ios/lib/client/client.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2021 Expo, Inc. 3 | * Copyright (c) 2018 Drifty Co. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | import type * as net from 'net'; 9 | 10 | import type { ProtocolClient } from '../protocol'; 11 | 12 | export abstract class ServiceClient { 13 | constructor(public socket: net.Socket, protected protocolClient: T) {} 14 | } 15 | 16 | export class ResponseError extends Error { 17 | constructor(msg: string, public response: any) { 18 | super(msg); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/xdl/src/apple/native-run/ios/lib/client/index.ts: -------------------------------------------------------------------------------- 1 | export * from './client'; 2 | export * from './afc'; 3 | export * from './debugserver'; 4 | export * from './installation_proxy'; 5 | export * from './lockdownd'; 6 | export * from './mobile_image_mounter'; 7 | export * from './usbmuxd'; 8 | -------------------------------------------------------------------------------- /packages/xdl/src/apple/native-run/ios/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './client'; 2 | export * from './protocol'; 3 | export * from './manager'; 4 | -------------------------------------------------------------------------------- /packages/xdl/src/apple/native-run/ios/lib/lib-errors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2021 Expo, Inc. 3 | * Copyright (c) 2018 Drifty Co. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | /** 10 | * Type union of error codes we get back from the protocol. 11 | */ 12 | export type IOSLibErrorCode = 'DeviceLocked'; 13 | 14 | export class IOSLibError extends Error implements NodeJS.ErrnoException { 15 | constructor(message: string, readonly code: IOSLibErrorCode) { 16 | super(message); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/xdl/src/apple/native-run/ios/lib/protocol/index.ts: -------------------------------------------------------------------------------- 1 | export * from './protocol'; 2 | export * from './afc'; 3 | export * from './gdb'; 4 | export * from './lockdown'; 5 | export * from './usbmux'; 6 | -------------------------------------------------------------------------------- /packages/xdl/src/apple/utils/waitForActionAsync.ts: -------------------------------------------------------------------------------- 1 | import { delayAsync } from '../../utils/delayAsync'; 2 | 3 | export class TimeoutError extends Error {} 4 | 5 | export async function waitForActionAsync({ 6 | action, 7 | interval = 100, 8 | maxWaitTime = 20000, 9 | }: { 10 | action: () => T | Promise; 11 | interval?: number; 12 | maxWaitTime?: number; 13 | }): Promise { 14 | let complete: T; 15 | const start = Date.now(); 16 | do { 17 | complete = await action(); 18 | 19 | await delayAsync(interval); 20 | if (Date.now() - start > maxWaitTime) { 21 | break; 22 | } 23 | } while (!complete); 24 | 25 | return complete; 26 | } 27 | -------------------------------------------------------------------------------- /packages/xdl/src/gating/FeatureGateEnvOverrides.ts: -------------------------------------------------------------------------------- 1 | import nullthrows from 'nullthrows'; 2 | 3 | import { Env, FeatureGateKey } from '../internal'; 4 | 5 | export default class FeatureGateEnvOverrides { 6 | private readonly map = new Map(); 7 | 8 | constructor() { 9 | const { enable, disable } = Env.getFeatureGateOverrides(); 10 | const overrideEnableGateKeys = new Set(enable); 11 | const overrideDisableGateKeys = new Set(disable); 12 | 13 | for (const overrideEnableKey of overrideEnableGateKeys) { 14 | if (overrideDisableGateKeys.has(overrideEnableKey)) { 15 | continue; 16 | } 17 | this.map.set(overrideEnableKey, true); 18 | } 19 | for (const overrideDisableGateKey of overrideDisableGateKeys) { 20 | this.map.set(overrideDisableGateKey, false); 21 | } 22 | } 23 | 24 | public isOverridden(key: FeatureGateKey): boolean { 25 | return this.map.has(key) ?? false; 26 | } 27 | 28 | public getOverride(key: FeatureGateKey): boolean { 29 | return nullthrows(this.map.get(key)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/xdl/src/gating/FeatureGateKey.ts: -------------------------------------------------------------------------------- 1 | export enum FeatureGateKey { 2 | // for tests 3 | TEST = 'test', 4 | } 5 | 6 | export const featureGateDefaultValueWhenNoServerValue: Record = { 7 | [FeatureGateKey.TEST]: true, 8 | }; 9 | -------------------------------------------------------------------------------- /packages/xdl/src/gating/FeatureGateTestOverrides.ts: -------------------------------------------------------------------------------- 1 | import { FeatureGateKey } from '../internal'; 2 | 3 | export const setOverride = (_key: FeatureGateKey, _enabled: boolean): void => { 4 | throw new Error('Must use mocked FeatureGateTestOverrides'); 5 | }; 6 | 7 | export const removeOverride = (_key: FeatureGateKey): void => { 8 | throw new Error('Must use mocked FeatureGateTestOverrides'); 9 | }; 10 | 11 | export const isOverridden = (_key: FeatureGateKey): boolean => false; 12 | 13 | export const getOverride = (_key: FeatureGateKey): boolean => { 14 | throw new Error('Must use mocked FeatureGateTestOverrides'); 15 | }; 16 | -------------------------------------------------------------------------------- /packages/xdl/src/gating/__mocks__/FeatureGateTestOverrides.ts: -------------------------------------------------------------------------------- 1 | import { FeatureGateKey } from '../../internal'; 2 | 3 | const map = new Map(); 4 | 5 | export const setOverride = (key: FeatureGateKey, enabled: boolean): void => { 6 | map.set(key, enabled); 7 | }; 8 | 9 | export const removeOverride = (key: FeatureGateKey): void => { 10 | map.delete(key); 11 | }; 12 | 13 | export const isOverridden = (key: FeatureGateKey): boolean => map.has(key); 14 | 15 | export const getOverride = (key: FeatureGateKey): boolean => { 16 | if (map.has(key)) { 17 | return map.get(key); 18 | } 19 | throw new Error(`Key ${key} not overridden`); 20 | }; 21 | -------------------------------------------------------------------------------- /packages/xdl/src/index.ts: -------------------------------------------------------------------------------- 1 | import { install as installSourceMapSupport } from 'source-map-support'; 2 | 3 | if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') { 4 | installSourceMapSupport(); 5 | } 6 | export { 7 | Analytics, 8 | Android, 9 | ApiV2, 10 | AppleDevice, 11 | Binaries, 12 | Config, 13 | AndroidCredentials, 14 | ConnectionStatus, 15 | CoreSimulator, 16 | Detach, 17 | Doctor, 18 | Env, 19 | EmbeddedAssets, 20 | Exp, 21 | ErrorCode, 22 | isDevClientPackageInstalled, 23 | IosCodeSigning, 24 | Logger, 25 | ModuleVersion, 26 | LoadingEvent, 27 | printBundleSizes, 28 | PackagerLogsStream, 29 | LoadingPageHandler, 30 | LogRecord, 31 | LogUpdater, 32 | PKCS12Utils, 33 | Project, 34 | Prompts, 35 | ProjectSettings, 36 | ProjectAssets, 37 | ProjectUtils, 38 | SimControl, 39 | Simulator, 40 | ThirdParty, 41 | UrlUtils, 42 | UserManager, 43 | User, 44 | RobotUser, 45 | UserSettings, 46 | UnifiedAnalytics, 47 | Versions, 48 | Webpack, 49 | XDLError, 50 | } from './internal'; 51 | -------------------------------------------------------------------------------- /packages/xdl/src/ip.ts: -------------------------------------------------------------------------------- 1 | import internalIp from 'internal-ip'; 2 | 3 | export default { 4 | address() { 5 | return internalIp.v4.sync() || '127.0.0.1'; 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/xdl/src/logs/TableText.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import prettyBytes from 'pretty-bytes'; 3 | import stripAnsi from 'strip-ansi'; 4 | import table from 'text-table'; 5 | 6 | export function createFilesTable(files: [string, string | Uint8Array][]): string { 7 | const tableData = files.map((item, index) => { 8 | const fileBranch = index === 0 ? '┌' : index === files.length - 1 ? '└' : '├'; 9 | 10 | return [`${fileBranch} ${item[0]}`, prettyBytes(Buffer.byteLength(item[1], 'utf8'))]; 11 | }); 12 | return table([['Bundle', 'Size'].map(v => chalk.underline(v)), ...tableData], { 13 | align: ['l', 'r'], 14 | stringLength: str => stripAnsi(str).length, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /packages/xdl/src/logs/TerminalLink.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import terminalLink from 'terminal-link'; 3 | 4 | /** 5 | * When linking isn't available, format the learn more link better. 6 | * 7 | * @example Learn more: https://expo.io 8 | * @example [Learn more](https://expo.io) 9 | * @param url 10 | */ 11 | export function learnMore(url: string): string { 12 | return terminalLink(chalk.underline('Learn more.'), url, { 13 | fallback: (text, url) => `Learn more: ${chalk.underline(url)}`, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /packages/xdl/src/logs/__tests__/__snapshots__/PackagerLogsStream-test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`PackagerLogsStream formats a Metro internal error 1`] = ` 4 | "node_modules/react-native/Libraries/NewAppScreen/components/logo.png: Cannot read property 'length' of undefined 5 | TypeError: Cannot read property 'length' of undefined 6 | at applyAssetDataPlugins (/app/node_modules/metro/src/Assets.js:182:25) 7 | at getAssetData (/app/node_modules/metro/src/Assets.js:178:16) 8 | at async Object.transform (/app/node_modules/metro-transform-worker/src/utils/assetTransformer.js:30:16) 9 | at async transformAsset (/app/node_modules/metro-transform-worker/src/index.js:371:18) 10 | at async Object.transform (/app/node_modules/metro-transform-worker/src/index.js:559:14)" 11 | `; 12 | 13 | exports[`PackagerLogsStream formats an application code syntax error 1`] = ` 14 | "SyntaxError: /app/App.js: Unexpected token (5:0) 15 | 16 | 3 | import { StyleSheet, Text, View } from 'react-native'; 17 | 4 | 18 | > 5 | > 19 | | ^ 20 | 6 | export default function App() { 21 | 7 | return ( 22 | 8 | " 23 | `; 24 | -------------------------------------------------------------------------------- /packages/xdl/src/project/__tests__/ExpSchema-test.ts: -------------------------------------------------------------------------------- 1 | import { ExpSchema } from '../../internal'; 2 | 3 | describe(`getAssetSchemasAsync return array of strings including some known values`, () => { 4 | test.each([ 5 | [ 6 | '38.0.0', 7 | ['icon', 'notification.icon', 'splash.image', 'ios.splash.xib', 'android.splash.xxhdpi'], 8 | ], 9 | [ 10 | 'UNVERSIONED', 11 | ['icon', 'notification.icon', 'splash.image', 'ios.splash.image', 'android.splash.xxhdpi'], 12 | ], 13 | ])('for SDK %s', async (sdkVersion, expectedAssetsPaths) => { 14 | const schemas = await ExpSchema.getAssetSchemasAsync(sdkVersion); 15 | expect(schemas.every(field => typeof field === 'string')).toBe(true); 16 | for (const el of expectedAssetsPaths) { 17 | expect(schemas).toContain(el); 18 | } 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/xdl/src/project/errors.ts: -------------------------------------------------------------------------------- 1 | import { XDLError } from '../internal'; 2 | 3 | export function assertValidProjectRoot(projectRoot: string) { 4 | if (!projectRoot) { 5 | throw new XDLError('NO_PROJECT_ROOT', 'No project root specified'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/xdl/src/reporter/index.ts: -------------------------------------------------------------------------------- 1 | import { serializeError } from 'serialize-error'; 2 | 3 | class LogReporter { 4 | update(event: any) { 5 | if (event.error instanceof Error) { 6 | event.error = serializeError(event.error); 7 | } 8 | 9 | console.log(JSON.stringify(event)); 10 | } 11 | } 12 | 13 | module.exports = LogReporter; 14 | -------------------------------------------------------------------------------- /packages/xdl/src/start/__tests__/__snapshots__/ManifestHandler-test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`getUnsignedManifestString returns a stringified manifest with the same shape a server-signed manifest 1`] = `"{\\"manifestString\\":\\"{\\\\\\"name\\\\\\":\\\\\\"Hello\\\\\\",\\\\\\"slug\\\\\\":\\\\\\"hello-world\\\\\\",\\\\\\"owner\\\\\\":\\\\\\"ownername\\\\\\",\\\\\\"version\\\\\\":\\\\\\"1.0.0\\\\\\",\\\\\\"platforms\\\\\\":[\\\\\\"ios\\\\\\"]}\\",\\"signature\\":\\"UNSIGNED\\"}"`; 4 | -------------------------------------------------------------------------------- /packages/xdl/src/start/__tests__/fixtures/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/packages/xdl/src/start/__tests__/fixtures/icon.png -------------------------------------------------------------------------------- /packages/xdl/src/start/getFreePortAsync.ts: -------------------------------------------------------------------------------- 1 | import freeportAsync from 'freeport-async'; 2 | 3 | import { XDLError } from '../internal'; 4 | 5 | export async function getFreePortAsync(rangeStart: number) { 6 | const port = await freeportAsync(rangeStart, { hostnames: [null, 'localhost'] }); 7 | if (!port) { 8 | throw new XDLError('NO_PORT_FOUND', 'No available port found'); 9 | } 10 | 11 | return port; 12 | } 13 | -------------------------------------------------------------------------------- /packages/xdl/src/start/ngrokUrl.ts: -------------------------------------------------------------------------------- 1 | export function domainify(s: string): string { 2 | return s 3 | .toLowerCase() 4 | .replace(/[^a-z0-9-]/g, '-') 5 | .replace(/^-+/, '') 6 | .replace(/-+$/, ''); 7 | } 8 | 9 | export function randomIdentifier(length: number = 6): string { 10 | const alphabet = '23456789qwertyuipasdfghjkzxcvbnm'; 11 | let result = ''; 12 | for (let i = 0; i < length; i++) { 13 | const j = Math.floor(Math.random() * alphabet.length); 14 | const c = alphabet.substr(j, 1); 15 | result += c; 16 | } 17 | return result; 18 | } 19 | 20 | export function someRandomness(): string { 21 | return [randomIdentifier(2), randomIdentifier(3)].join('-'); 22 | } 23 | -------------------------------------------------------------------------------- /packages/xdl/src/tools/ArtifactUtils.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | 4 | import { Logger as logger } from '../internal'; 5 | 6 | export async function writeArtifactSafelyAsync( 7 | projectRoot: string, 8 | keyName: string | null, 9 | artifactPath: string, 10 | artifact: string | Uint8Array 11 | ) { 12 | const pathToWrite = path.resolve(projectRoot, artifactPath); 13 | if (!fs.existsSync(path.dirname(pathToWrite))) { 14 | const errorMsg = keyName 15 | ? `app.json specifies: ${pathToWrite}, but that directory does not exist.` 16 | : `app.json specifies ${keyName}: ${pathToWrite}, but that directory does not exist.`; 17 | logger.global.warn(errorMsg); 18 | } else { 19 | await fs.writeFile(pathToWrite, artifact); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/xdl/src/tools/ModuleVersion.ts: -------------------------------------------------------------------------------- 1 | import latestVersionAsync from 'latest-version'; 2 | import pTimeout from 'p-timeout'; 3 | import npmPackageJson from 'package-json'; 4 | import semver from 'semver'; 5 | 6 | import { FsCache } from '../internal'; 7 | 8 | /** @deprecated just use the update-check npm package */ 9 | function createModuleVersionChecker(name: string, currentVersion: string) { 10 | const UpdateCacher = new FsCache.Cacher( 11 | async () => { 12 | const pkgJson = await pTimeout(npmPackageJson(name, { version: currentVersion }), 2000); 13 | return { 14 | latestVersion: await pTimeout(latestVersionAsync(name), 2000), 15 | deprecated: pkgJson.deprecated, 16 | }; 17 | }, 18 | `${name}-${currentVersion}-updates.json`, 19 | 24 * 60 * 60 * 1000 // one day 20 | ); 21 | 22 | async function checkAsync() { 23 | const { latestVersion, deprecated } = await UpdateCacher.getAsync(); 24 | return { 25 | updateIsAvailable: semver.gt(latestVersion, currentVersion), 26 | latest: latestVersion, 27 | current: currentVersion, 28 | deprecated, 29 | }; 30 | } 31 | 32 | return { checkAsync }; 33 | } 34 | 35 | export { createModuleVersionChecker }; 36 | -------------------------------------------------------------------------------- /packages/xdl/src/tools/resolveEntryPoint.ts: -------------------------------------------------------------------------------- 1 | import { ProjectConfig } from '@expo/config'; 2 | import { getEntryPoint } from '@expo/config/paths'; 3 | import path from 'path'; 4 | 5 | const supportedPlatforms = ['ios', 'android', 'web']; 6 | 7 | export function resolveEntryPoint( 8 | projectRoot: string, 9 | platform?: string, 10 | projectConfig?: ProjectConfig 11 | ): string { 12 | if (platform && !supportedPlatforms.includes(platform)) { 13 | throw new Error( 14 | `Failed to resolve the project's entry file: The platform "${platform}" is not supported.` 15 | ); 16 | } 17 | // TODO: Bacon: support platform extension resolution like .ios, .native 18 | // const platforms = [platform, 'native'].filter(Boolean) as string[]; 19 | const platforms: string[] = []; 20 | 21 | const entry = getEntryPoint(projectRoot, ['./index'], platforms, projectConfig); 22 | if (!entry) 23 | throw new Error( 24 | `The project entry file could not be resolved. Please either define it in the \`package.json\` (main), \`app.json\` (expo.entryPoint), create an \`index.js\`, or install the \`expo\` package.` 25 | ); 26 | 27 | return path.relative(projectRoot, entry); 28 | } 29 | -------------------------------------------------------------------------------- /packages/xdl/src/utils/Semaphore.ts: -------------------------------------------------------------------------------- 1 | export class Semaphore { 2 | queue: ((v: boolean) => void)[] = []; 3 | available = 1; 4 | 5 | async acquire(): Promise { 6 | if (this.available > 0) { 7 | this.available -= 1; 8 | return Promise.resolve(true); 9 | } 10 | 11 | // If there is no permit available, we return a promise that resolves once the semaphore gets 12 | // signaled enough times that "available" is equal to one. 13 | return new Promise(resolver => this.queue.push(resolver)); 14 | } 15 | 16 | release() { 17 | this.available += 1; 18 | 19 | if (this.available > 1 && this.queue.length > 0) { 20 | throw new Error('this.available should never be > 0 when there is someone waiting.'); 21 | } else if (this.available === 1 && this.queue.length > 0) { 22 | // If there is someone else waiting, immediately consume the permit that was released 23 | // at the beginning of this function and let the waiting function resume. 24 | this.available -= 1; 25 | 26 | const nextResolver = this.queue.shift(); 27 | if (nextResolver) { 28 | nextResolver(true); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/xdl/src/utils/delayAsync.ts: -------------------------------------------------------------------------------- 1 | export function delayAsync(ms: number): Promise { 2 | return new Promise(resolve => setTimeout(resolve, ms)); 3 | } 4 | -------------------------------------------------------------------------------- /packages/xdl/src/utils/downloadApkAsync.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | 4 | import { downloadAppAsync, UserSettings, Versions } from '../internal'; 5 | 6 | function _apkCacheDirectory() { 7 | const dotExpoHomeDirectory = UserSettings.dotExpoHomeDirectory(); 8 | const dir = path.join(dotExpoHomeDirectory, 'android-apk-cache'); 9 | fs.mkdirpSync(dir); 10 | return dir; 11 | } 12 | 13 | export async function downloadApkAsync( 14 | url?: string, 15 | downloadProgressCallback?: (roundedProgress: number) => void 16 | ) { 17 | if (!url) { 18 | const versions = await Versions.versionsAsync(); 19 | url = versions.androidUrl; 20 | } 21 | 22 | const filename = path.parse(url).name; 23 | const apkPath = path.join(_apkCacheDirectory(), `${filename}.apk`); 24 | 25 | if (await fs.pathExists(apkPath)) { 26 | return apkPath; 27 | } 28 | 29 | await downloadAppAsync(url, apkPath, undefined, downloadProgressCallback); 30 | return apkPath; 31 | } 32 | -------------------------------------------------------------------------------- /packages/xdl/src/utils/isDevClientPackageInstalled.ts: -------------------------------------------------------------------------------- 1 | import resolveFrom from 'resolve-from'; 2 | 3 | export function isDevClientPackageInstalled(projectRoot: string) { 4 | try { 5 | // we check if `expo-dev-launcher` is installed instead of `expo-dev-client` 6 | // because someone could install only launcher. 7 | resolveFrom(projectRoot, 'expo-dev-launcher'); 8 | return true; 9 | } catch { 10 | return false; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/xdl/src/utils/parseBinaryPlistAsync.ts: -------------------------------------------------------------------------------- 1 | import plist from '@expo/plist'; 2 | // @ts-ignore 3 | import binaryPlist from 'bplist-parser'; 4 | import fs from 'fs'; 5 | 6 | const CHAR_CHEVRON_OPEN = 60; 7 | const CHAR_B_LOWER = 98; 8 | // .mobileprovision 9 | // const CHAR_ZERO = 30; 10 | 11 | export async function parseBinaryPlistAsync(plistPath: string) { 12 | return parsePlistBuffer(await fs.promises.readFile(plistPath)); 13 | } 14 | 15 | export function parsePlistBuffer(contents: Buffer) { 16 | if (contents[0] === CHAR_CHEVRON_OPEN) { 17 | const info = plist.parse(contents.toString()); 18 | if (Array.isArray(info)) return info[0]; 19 | return info; 20 | } else if (contents[0] === CHAR_B_LOWER) { 21 | const info = binaryPlist.parseBuffer(contents); 22 | if (Array.isArray(info)) return info[0]; 23 | return info; 24 | } else { 25 | throw new Error(`Cannot parse plist of type byte (0x${contents[0].toString(16)})`); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/xdl/src/webpack-utils/WebpackEnvironment.ts: -------------------------------------------------------------------------------- 1 | import getenv from 'getenv'; 2 | 3 | export const WEB_HOST = getenv.string('WEB_HOST', '0.0.0.0'); 4 | 5 | export const DEFAULT_PORT = getenv.int('WEB_PORT', 19006); 6 | 7 | // When you have errors in the production build that aren't present in the development build you can use `EXPO_WEB_DEBUG=true expo start --no-dev` to debug those errors. 8 | // - Prevent the production build from being minified 9 | // - Include file path info comments in the bundle 10 | export function isDebugModeEnabled(): boolean { 11 | return getenv.boolish('EXPO_WEB_DEBUG', false); 12 | } 13 | -------------------------------------------------------------------------------- /packages/xdl/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base", 3 | "include": [ 4 | "src/**/*.ts" 5 | ], 6 | "exclude": [ 7 | "**/__mocks__/**", 8 | "**/__tests__/**", 9 | "**/__integration_tests__/**" 10 | ], 11 | "compilerOptions": { 12 | "outDir": "build", 13 | "rootDir": "src" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ts-declarations/better-opn/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'better-opn' { 2 | function open( 3 | target: string, 4 | options?: any 5 | ): Promise; 6 | export = open; 7 | } 8 | -------------------------------------------------------------------------------- /ts-declarations/decache/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'decache' { 2 | function decache(moduleName: string): void; 3 | export = decache; 4 | } 5 | -------------------------------------------------------------------------------- /ts-declarations/exec-async/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'exec-async' { 2 | import { ExecFileOptions } from 'child_process'; 3 | 4 | export type ExecAsyncOptions = ExecFileOptions; 5 | 6 | export default function execAsync( 7 | command: string, 8 | args?: ReadonlyArray | object | undefined, 9 | options?: ExecAsyncOptions 10 | ): Promise; 11 | } 12 | -------------------------------------------------------------------------------- /ts-declarations/expo__simple-spinner/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@expo/simple-spinner' { 2 | export function start(interval?: number): void; 3 | export function stop(): void; 4 | export function change_sequence(seq: string[]): void; 5 | } 6 | -------------------------------------------------------------------------------- /ts-declarations/freeport-async/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'freeport-async' { 2 | interface FreePortOptions { 3 | hostnames?: Array; 4 | } 5 | 6 | function freePortAsync(rangeStart: number, options?: FreePortOptions): Promise; 7 | export = freePortAsync; 8 | } 9 | -------------------------------------------------------------------------------- /ts-declarations/hasbin/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'hasbin' { 2 | function hasbin(bin: string, done: (result: boolean) => void): void; 3 | 4 | export = hasbin; 5 | } 6 | -------------------------------------------------------------------------------- /ts-declarations/json-schema-traverse/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'json-schema-traverse' { 2 | type Schema = { [key: string]: any }; 3 | type Options = { allKeys?: boolean }; 4 | type Visitor = ( 5 | schema: Schema, 6 | jsonPtr: string, 7 | rootSchema: Schema, 8 | parentJsonPtr: string, 9 | parentKeyword: string, 10 | parentSchema: Schema, 11 | keyIndex: string 12 | ) => void; 13 | 14 | function traverse(schema: Schema, cb: Visitor): void; 15 | function traverse(schema: Schema, opts: Options, cb: Visitor): void; 16 | export = traverse; 17 | } 18 | -------------------------------------------------------------------------------- /ts-declarations/md5hex/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'md5hex' { 2 | interface MD5HexOptions { 3 | salt?: string; 4 | saltPrefix?: string; 5 | saltSuffix?: string; 6 | length?: number; 7 | } 8 | 9 | function md5hex(stringOrBuffer: string | Buffer, opts?: MD5HexOptions | number): string; 10 | 11 | export = md5hex; 12 | } 13 | -------------------------------------------------------------------------------- /ts-declarations/mini-css-extract-plugin/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'mini-css-extract-plugin'; 2 | -------------------------------------------------------------------------------- /ts-declarations/pnp-webpack-plugin/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'pnp-webpack-plugin' { 2 | function apply(resolver: any /* EnhancedResolve.Resolver */): void; 3 | function moduleLoader(module: NodeModule): any; 4 | function forkTsCheckerOptions(config: any): any; 5 | } 6 | -------------------------------------------------------------------------------- /ts-declarations/postcss-safe-parser/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'postcss-safe-parser' { 2 | import { parse } from 'postcss'; 3 | var parser: typeof parse; 4 | export = parser; 5 | } 6 | -------------------------------------------------------------------------------- /ts-declarations/probe-image-size/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'probe-image-size' { 2 | import { ReadStream } from 'fs'; 3 | 4 | type ProbeResult = { 5 | width: number; 6 | height: number; 7 | type: 'bmp' | 'gif' | 'jpg' | 'png' | 'psd' | 'svg' | 'tiff' | 'webp'; 8 | mime: string; 9 | wUnits: 'in' | 'mm' | 'cm' | 'pt' | 'pc' | 'px' | 'em' | 'ex'; 10 | hUnits: 'in' | 'mm' | 'cm' | 'pt' | 'pc' | 'px' | 'em' | 'ex'; 11 | }; 12 | 13 | function probeImageSize( 14 | src: ReadStream | string, 15 | options?: any, 16 | callback?: (error: Error | null, result: ProbeResult) => void 17 | ): Promise; 18 | 19 | namespace probeImageSize { 20 | function sync(buffer: Buffer): ProbeResult | null; 21 | } 22 | 23 | export = probeImageSize; 24 | } 25 | -------------------------------------------------------------------------------- /ts-declarations/qrcode-terminal/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'qrcode-terminal' { 2 | export function generate(url: string, opts: { small: boolean }, cb: (code: string) => void): void; 3 | } 4 | -------------------------------------------------------------------------------- /ts-declarations/read-last-lines/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'read-last-lines' { 2 | namespace readLastLines { 3 | function read( 4 | inputFilePath: string, 5 | maxLineCount: number, 6 | encoding?: string 7 | ): Promise; 8 | } 9 | export = readLastLines; 10 | } 11 | -------------------------------------------------------------------------------- /ts-declarations/slugid/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'slugid' { 2 | function encode(uuid: string): string; 3 | function decode(slug: string): string; 4 | function v4(): string; 5 | function nice(): string; 6 | } 7 | -------------------------------------------------------------------------------- /ts-declarations/update-check/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'update-check'; 2 | -------------------------------------------------------------------------------- /ts-declarations/webpack-deep-scope-plugin/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'webpack-deep-scope-plugin'; 2 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node12/tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "composite": true, 6 | "inlineSources": true, 7 | "moduleResolution": "node", 8 | "noImplicitReturns": true, 9 | "resolveJsonModule": true, 10 | "sourceMap": true, 11 | "typeRoots": ["./ts-declarations", "./node_modules/@types", "../../node_modules/@types", "../../ts-declarations"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /unlinked-packages/README.md: -------------------------------------------------------------------------------- 1 | # unlinked-packages 2 | 3 | Packages in this directory are published independently of other packages in the repository and should not be symlinked in development. They may be depended on by packages in `packages/` but should be consciously and carefully versioned. It's likely that libraries here should be moved somewhere else or need to be fundamentally re-designed, eg: configure-splash-screen lives here because it only supports one very specific version of expo-splash-screen at any given time, but it should probably support multiple versions of expo-splash-screen. 4 | 5 | The publish script used for `packages` will not publish packages from this directory. They should be published manually when needed, and updates to other libraries that depend on them should be updated manually. 6 | -------------------------------------------------------------------------------- /unlinked-packages/configure-splash-screen/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['universe/node'], 4 | overrides: [ 5 | { 6 | files: ['**/__tests__/*'], 7 | }, 8 | ], 9 | }; 10 | -------------------------------------------------------------------------------- /unlinked-packages/configure-splash-screen/__mocks__/fs.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { fs } from 'memfs'; 3 | module.exports = fs; 4 | -------------------------------------------------------------------------------- /unlinked-packages/configure-splash-screen/__mocks__/xcode.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { fs } from 'memfs'; 3 | 4 | const { project } = jest.requireActual('xcode'); 5 | const parser = require('xcode/lib/parser/pbxproj'); 6 | 7 | // Original implementation delegates parsing to subprocess and I couldn't find how to mock `fs` in subprocess 8 | // It can be found in `xcode/lib/parserJob` 9 | project.prototype.parse = function (cb) { 10 | try { 11 | const fileContents = fs.readFileSync(this.filepath, 'utf-8'); 12 | this.hash = parser.parse(fileContents); 13 | cb(this.hash); 14 | } catch (e) { 15 | cb(null, e); 16 | } 17 | 18 | return this; 19 | }; 20 | 21 | module.exports = { project }; 22 | -------------------------------------------------------------------------------- /unlinked-packages/configure-splash-screen/jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | preset: '../../jest/unit-test-config', 5 | rootDir: path.resolve(__dirname), 6 | displayName: require('./package').name, 7 | }; 8 | -------------------------------------------------------------------------------- /unlinked-packages/configure-splash-screen/src/__tests__/fixtures/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/unlinked-packages/configure-splash-screen/src/__tests__/fixtures/background.png -------------------------------------------------------------------------------- /unlinked-packages/configure-splash-screen/src/__tests__/fixtures/background_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/expo-cli/54997b9b1aa66329f91e33e913f98155bcbb2464/unlinked-packages/configure-splash-screen/src/__tests__/fixtures/background_dark.png -------------------------------------------------------------------------------- /unlinked-packages/configure-splash-screen/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const SplashScreenImageResizeMode = { 2 | CONTAIN: 'contain', 3 | COVER: 'cover', 4 | NATIVE: 'native', 5 | } as const; 6 | export type SplashScreenImageResizeModeType = TypeFromConstObject< 7 | typeof SplashScreenImageResizeMode 8 | >; 9 | 10 | export const Platform = { 11 | ANDROID: 'android', 12 | IOS: 'ios', 13 | ALL: 'all', 14 | } as const; 15 | export type PlatformType = TypeFromConstObject; 16 | 17 | export const SplashScreenStatusBarStyle = { 18 | DEFAULT: 'default', 19 | LIGHT_CONTENT: 'light-content', 20 | DARK_CONTENT: 'dark-content', 21 | } as const; 22 | export type SplashScreenStatusBarStyleType = TypeFromConstObject; 23 | 24 | type TypeFromConstObject = T[keyof T]; 25 | -------------------------------------------------------------------------------- /unlinked-packages/configure-splash-screen/src/index-cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import createCommand from './cli-command'; 4 | 5 | async function runAsync() { 6 | try { 7 | await createCommand().parseAsync(process.argv); 8 | } catch (e: any) { 9 | console.error(`\n${e.message}\n`); 10 | process.exit(1); 11 | } 12 | } 13 | 14 | runAsync(); 15 | -------------------------------------------------------------------------------- /unlinked-packages/configure-splash-screen/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as configureIosSplashScreen } from './ios'; 2 | export { default as configureAndroidSplashScreen } from './android'; 3 | 4 | export { SplashScreenImageResizeMode, SplashScreenStatusBarStyle } from './constants'; 5 | export { 6 | IosSplashScreenConfigJSON as IosSplashScreenConfig, 7 | AndroidSplashScreenConfigJSON as AndroidSplashScreenConfig, 8 | } from './SplashScreenConfig'; 9 | -------------------------------------------------------------------------------- /unlinked-packages/configure-splash-screen/src/ios/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | 3 | import { IosSplashScreenConfigJSON } from '../SplashScreenConfig'; 4 | import { validateIosConfig } from '../validators'; 5 | import configureBackgroundAsset from './BackgroundAsset'; 6 | import configureImageAsset from './ImageAsset'; 7 | import configureInfoPlist from './Info.plist'; 8 | import configureStoryboard from './Storyboard'; 9 | import readPbxProject from './pbxproj'; 10 | 11 | export default async function configureIos( 12 | projectRootPath: string, 13 | config: IosSplashScreenConfigJSON 14 | ) { 15 | const validatedConfig = await validateIosConfig(config); 16 | 17 | const iosProject = await readPbxProject(projectRootPath); 18 | 19 | await Promise.all([ 20 | configureInfoPlist(iosProject.projectPath, validatedConfig), 21 | configureImageAsset(iosProject.projectPath, validatedConfig), 22 | configureBackgroundAsset(iosProject.projectPath, validatedConfig), 23 | configureStoryboard(iosProject, validatedConfig), 24 | ]); 25 | 26 | await fs.writeFile(iosProject.pbxProject.filepath, iosProject.pbxProject.writeSync()); 27 | } 28 | -------------------------------------------------------------------------------- /unlinked-packages/configure-splash-screen/src/utils/StateManager.ts: -------------------------------------------------------------------------------- 1 | export default class StateManager< 2 | StateType, 3 | AppliedActionResultType, 4 | ActionName extends string = never 5 | > { 6 | constructor(public state: StateType) {} 7 | // @ts-ignore 8 | appliedActions: { [K in ActionName]: AppliedActionResultType } = {}; 9 | // @ts-ignore 10 | applyAction: ( 11 | action: ( 12 | content: StateType, 13 | actions: { [K in ActionName]: AppliedActionResultType } 14 | ) => [StateType, NewActionName, AppliedActionResultType] 15 | ) => StateManager = action => { 16 | const [state, actionName, appliedAction] = action(this.state, this.appliedActions); 17 | this.state = state; 18 | // @ts-ignore 19 | this.appliedActions[actionName] = appliedAction; 20 | return this; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /unlinked-packages/configure-splash-screen/src/utils/file-utils.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | 4 | /** 5 | * Creates file with given content with possible parent directories creation. 6 | */ 7 | export async function createDirAndWriteFile(filePath: string, content: string) { 8 | if (!(await fs.pathExists(path.dirname(filePath)))) { 9 | await fs.mkdirp(path.dirname(filePath)); 10 | } 11 | await fs.writeFile(filePath, content); 12 | } 13 | 14 | /** 15 | * Reads given file as UTF-8 with fallback to given content when file is not found. 16 | */ 17 | export async function readFileWithFallback(filePath: string, fallbackContent?: string) { 18 | if (await fs.pathExists(filePath)) { 19 | return fs.readFile(filePath, 'utf-8'); 20 | } 21 | if (fallbackContent) { 22 | return fallbackContent; 23 | } 24 | throw Error(`File not found ${filePath}`); 25 | } 26 | 27 | export async function removeFileIfExists(filePath: string) { 28 | if (await fs.pathExists(filePath)) { 29 | await fs.unlink(filePath); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /unlinked-packages/configure-splash-screen/src/xcode/index.ts: -------------------------------------------------------------------------------- 1 | import { XcodeProject, UUID } from 'xcode'; 2 | 3 | /** 4 | * @param filePath 5 | * @param param1.target PBXNativeTarget reference 6 | * @param param1.group PBXGroup reference 7 | */ 8 | export function addStoryboardFileToProject( 9 | pbxProject: XcodeProject, 10 | filePath: string, 11 | { target, group }: { target: UUID; group: UUID } 12 | ) { 13 | const file = pbxProject.addFile(filePath, group, { 14 | lastKnownFileType: 'file.storyboard', 15 | defaultEncoding: 4, 16 | target, 17 | }); 18 | if (!file) { 19 | throw new Error('File already exists in the project'); 20 | } 21 | delete pbxProject.pbxFileReferenceSection()[file.fileRef].explicitFileType; 22 | delete pbxProject.pbxFileReferenceSection()[file.fileRef].includeInIndex; 23 | 24 | file.uuid = pbxProject.generateUuid(); 25 | file.target = target; 26 | 27 | pbxProject.addToPbxBuildFileSection(file); 28 | pbxProject.addToPbxResourcesBuildPhase(file); 29 | } 30 | -------------------------------------------------------------------------------- /unlinked-packages/configure-splash-screen/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./build", 6 | "esModuleInterop": true, 7 | "resolveJsonModule": false 8 | }, 9 | "include": ["src/**/*"], 10 | "exclude": ["**/__mocks__/*", "**/__tests__/*"] 11 | } 12 | --------------------------------------------------------------------------------