profile
viewpoint

expo/create-react-native-app 12353

Create React Native apps that run on iOS, Android, and web

expo/ngrok 11

simple node wrapper for ngrok

fson/cljs-fiddle 2

ClojureScript live preview.

fson/coffeescript-style-guide 2

Best-practices and coding conventions for the CoffeeScript programming language

fson/chosen 1

Chosen is a library for making long, unwieldy select boxes more friendly.

fson/ad 0

Making Active Directory jQuery-easy

fson/Anypic 0

An open source mobile and web app that lets users share photos similar to Instagram

fson/apollo-client 0

:rocket: A fully-featured caching GraphQL client for any server or UI framework

push eventexpo/eas-cli

Ville Immonen

commit sha d468c29f1cc4336a1824039a52934ad211dc3c19

Add error message on failure

view details

push time in a day

Pull request review commentexpo/eas-cli

Add Bash installer

+#!/bin/bash++# This script downloads the latest release version of EAS CLI from GitHub and+# installs it in /usr/local/bin++{+  # All code is inside a block to ensure the script executes only when+  # downloaded completely.++  set -e++  prefix="${EAS_PREFIX:-/usr/local}"

I've had problems with tools that use this profile modification approach in the past. E.g. we only add it to .zshrc or .zprofile because the user is using Zsh and/or because those files exist. Now if the use (even temporarily) switches to Bash (sometimes it's a script written by someone else, possibly running inside a build phase of Xcode, in a VS Code plugin, possibilities are endless..), the user will just get an error like bash: eas: command not found and it's not obvious at all why this is happening or what they should do to fix it.

There are many files too: .profile, .bashrc, .bash_profile, .zshrc and .zprofile... just for Bash and Zsh. Which one would we pick? Search in all of them in case the user previously added the snippet manually?

Additionally, I wouldn't copy nvm too closely: not sure how it does it but its startup script adds a visible delay to opening terminal Windows which is why I switched away from nvm a long time ago.

These are tradeoffs, but since I was aiming to create a single-command, reliable way to install the CLI, having to sometimes type your password seems less bad than having the script randomly fail or require manual steps. The current approach also works well in CI systems where the code is already running as root.

fson

comment created time in a day

PullRequestReviewEvent

Pull request review commentexpo/eas-cli

Add Bash installer

+name: Run installer tests+on:+  push:+    branches: [main]+  pull_request:+    types: [opened, synchronize]++jobs:+  test:+    runs-on: ubuntu-latest

I've added macOS to this test now. I can do WSL in a separate PR once I figure out how to invoke it from a GH action :)

fson

comment created time in a day

PullRequestReviewEvent

Pull request review commentexpo/eas-cli

Add Bash installer

 USAGE   $ eas build ``` -_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.0.0/src/commands/build/index.ts)_+_See code: [build/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.0.0/build/commands/build/index.ts)_

These lines were automatically added, by the oclif-dev readme command, not sure why it included the paths from build output. Re-running it seems to have removed them.

fson

comment created time in a day

PullRequestReviewEvent

push eventexpo/eas-cli

Ville Immonen

commit sha ea1d8d21273f7bb3a4eff297a05fb41f2b1ea2e1

Remove incorrect code paths from readme

view details

push time in a day

push eventexpo/eas-cli

Ville Immonen

commit sha ffb9b5840c4a760426821905877928329586319f

Only show the sudo warning if not already root

view details

push time in a day

push eventexpo/eas-cli

Ville Immonen

commit sha 1825085f9987af6eff9ebe596aa6b7764e2f6bba

Update bin/install.sh Co-authored-by: James Ide <ide@users.noreply.github.com>

view details

push time in a day

push eventexpo/eas-cli

Ville Immonen

commit sha d3dbd4ecfc59668aa716ed4897308d97e80c0c2d

Update bin/install.sh Co-authored-by: James Ide <ide@users.noreply.github.com>

view details

push time in a day

Pull request review commentexpo/eas-cli

Add Bash installer

+#!/bin/bash++# This script downloads the latest release version of EAS CLI from GitHub and+# installs it in /usr/local/bin++{+  # All code is inside a block to ensure the script executes only when+  # downloaded completely.++  set -e++  prefix="${EAS_PREFIX:-/usr/local}"+  temp_dir="$(mktemp -d)"+  +  if  [ -t 1 ] && [ -z "$NO_COLOR" ]+  then+    # Use colors when stdout is a terminal and NO_COLOR isn't set.+    text_bold=$'\033[1m'+    text_normal=$'\033[22m'+    text_red=$'\033[31m'+    text_default_color=$'\033[39m'+  else+    # Disable colors+    text_bold=""+    text_normal=""+    text_red=""+    text_default_color=""+  fi++  abort() {+    echo -e "$text_bold$text_red$1$text_normal$text_default_color"+    exit 1+  }++  install() {+    needs_sudo=""+    # Make sure path exists and can be written to by the current user.+    if ! mkdir -p "$prefix/lib" || [[ ! -w "$prefix/lib" ]]+    then+      needs_sudo=true+    fi+    if ! mkdir -p "$prefix/bin" || [[ ! -w "$prefix/bin" ]]+    then+      needs_sudo=true+    fi+    if ! rm -rf "$prefix/lib/eas"+    then+      needs_sudo=true+    fi+    # Run commands with sudo if necessary.+    if [ -n "$needs_sudo" ]+    then+      echo "Installing requires superuser access."+      echo "The sudo command will prompt for your password."+      sudo rm -rf "$prefix/lib/eas"+      sudo mv "$temp_dir/eas" "$prefix/lib/eas"+      sudo ln -fs "$prefix/lib/eas/bin/eas" "$prefix/bin/eas"+    else+      +      mv "$temp_dir/eas" "$prefix/lib/eas"+      ln -fs "$prefix/lib/eas/bin/eas" "$prefix/bin/eas"+    fi+  }++  if [[ ! ":$PATH:" == *":$prefix/bin:"* ]]; then+    abort "Your path is missing $prefix/bin, you need to add this to use this installer."+  fi+  +  hardware="$(uname -m)"+  case "$hardware" in+    x86_64)+      arch="x64"+      ;;+    arm*)+      arch="arm"+      ;;+    *)+      abort "Unsupported CPU architecture $hardware."+      ;;+  esac++  case "$(uname -s)" in+    Darwin*)+      platform="darwin"+      if [[ "$arch" != "x64" ]]; then+        abort "Unsupported CPU architecture $hardware on macOS."+      fi+      ;;+    Linux*)+      platform="linux"+      ;;+    *)+      abort "This installer is only supported on macOS, Linux and Windows Subsystem for Linux."

Oxford comma ftw

fson

comment created time in a day

PullRequestReviewEvent

push eventexpo/eas-cli

Ville Immonen

commit sha 79a9fe7b356822991cac4f7f51e88ce7a8e4c0cf

Test on Ubuntu and macOS

view details

push time in a day

PullRequestReviewEvent

Pull request review commentexpo/eas-cli

Add Bash installer

 OPTIONS   --status=(in-queue|in-progress|errored|finished) ``` -_See code: [src/commands/build/status.ts](https://github.com/expo/eas-cli/blob/v0.0.0/src/commands/build/status.ts)_+_See code: [build/commands/build/status.ts](https://github.com/expo/eas-cli/blob/v0.0.0/build/commands/build/status.ts)_++## `eas credentials`++Manage your credentials++```+USAGE+  $ eas credentials+```++_See code: [build/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.0.0/build/commands/credentials.ts)_++## `eas device:create`++register new Apple Devices to use for internal distribution

These are generated from the descriptions of commands, so I'm leaving changing this outside this PR. https://github.com/expo/eas-cli/blob/9b807bb2da5263df7cc663e60d7f64d728bcfe87/packages/eas-cli/src/commands/device/create.ts#L9

fson

comment created time in a day

PullRequestReviewEvent

Pull request review commentexpo/eas-cli

[eas-cli] Move all GraphQL to one directory

+import gql from 'graphql-tag';++import { graphqlClient, withErrorHandlingAsync } from '../client';+import { Project } from '../types/Project';++type ProjectIdType = Pick<Project, 'id'>;++export class ProjectQuery {+  static async idByUsernameAndSlugAsync(username: string, slug: string): Promise<ProjectIdType> {+    const data = await withErrorHandlingAsync(+      graphqlClient+        .query<{ project: { byUsernameAndSlug: ProjectIdType } }>(+          gql`+          {+            project {+              byUsernameAndSlug(username: "${username}", slug: "${slug}", sdkVersions: []) {
              byUsernameAndSlug(username: $username, slug: $slug) {

You can also omit sdkVersions because it's an optional parameter.

barthap

comment created time in a day

PullRequestReviewEvent

Pull request review commentexpo/eas-cli

[eas-cli] Move all GraphQL to one directory

+import gql from 'graphql-tag';++import { graphqlClient, withErrorHandlingAsync } from '../client';+import { Project } from '../types/Project';++type ProjectIdType = Pick<Project, 'id'>;++export class ProjectQuery {+  static async idByUsernameAndSlugAsync(username: string, slug: string): Promise<ProjectIdType> {+    const data = await withErrorHandlingAsync(+      graphqlClient+        .query<{ project: { byUsernameAndSlug: ProjectIdType } }>(+          gql`+          {+            project {+              byUsernameAndSlug(username: "${username}", slug: "${slug}", sdkVersions: []) {

Please use variables.

barthap

comment created time in a day

Pull request review commentexpo/eas-cli

[eas-cli] Move all GraphQL to one directory

+import gql from 'graphql-tag';++import { graphqlClient, withErrorHandlingAsync } from '../client';+import { Build } from '../types/Build';++type Filters = Partial<Pick<Build, 'platform' | 'status'>> & {+  order?: number;+  limit?: number;+};++type ArtifactFragmentType = Pick<Build, 'artifacts'>;+type PlatformAndArtifactFragmentType = Pick<Build, 'platform' | 'artifacts'>;++export class BuildQuery {+  static async forArtifactByIdAsync(buildId: string): Promise<PlatformAndArtifactFragmentType> {+    const data = await withErrorHandlingAsync(+      graphqlClient+        .query<{ builds: { byId: PlatformAndArtifactFragmentType } }>(+          gql`+          {+            builds {+              byId(buildId: "${buildId}") {+                platform,+                artifacts {+                  buildUrl+                }+              }+            }+          }`+        )+        .toPromise()+    );++    return data.builds.byId;+  }++  static async allArtifactsForAppAsync(+    appId: string,+    filters?: Filters+  ): Promise<ArtifactFragmentType[]> {+    const filterData = [`appId: "${appId}"`];++    if (filters?.limit) {+      filterData.push(`limit: ${filters.limit}`);+    }+    if (filters?.order) {+      filterData.push(`order: ${filters.order}`);+    }+    if (filters?.platform) {+      filterData.push(`platform: ${filters.platform}`);+    }+    if (filters?.status) {+      filterData.push(`status: ${filters.status}`);+    }++    const data = await withErrorHandlingAsync(+      graphqlClient+        .query<{ builds: { allForApp: ArtifactFragmentType[] } }>(+          gql`+          {+            builds {+              allForApp(${filterData.join(', ')}) {+                artifacts {+                  buildUrl+                }+              }+            }+          }`
          query ($appId: String!, $limit: Int, $offset: Int, $platform: AppPlatform, $status: BuildStatus) {
            builds {
              allForApp(appId: $appId, limit: $limit, offset: $offset, platform: $platform, status: $status) {
                artifacts {
                  buildUrl
                }
              }
            }
          }, { ...filters, appId }

Variables should be used here as well. A nice thing about this is that it eliminates the need to generate GraphQL from string snippets.

barthap

comment created time in a day

Pull request review commentexpo/eas-cli

[eas-cli] Move all GraphQL to one directory

+import gql from 'graphql-tag';++import { graphqlClient, withErrorHandlingAsync } from '../client';+import { Build } from '../types/Build';++type Filters = Partial<Pick<Build, 'platform' | 'status'>> & {+  order?: number;+  limit?: number;+};++type ArtifactFragmentType = Pick<Build, 'artifacts'>;+type PlatformAndArtifactFragmentType = Pick<Build, 'platform' | 'artifacts'>;++export class BuildQuery {+  static async forArtifactByIdAsync(buildId: string): Promise<PlatformAndArtifactFragmentType> {+    const data = await withErrorHandlingAsync(+      graphqlClient+        .query<{ builds: { byId: PlatformAndArtifactFragmentType } }>(+          gql`+          {+            builds {+              byId(buildId: "${buildId}") {+                platform,+                artifacts {+                  buildUrl+                }+              }+            }+          }`

Rather than string interpolation, please use variables to pass parameters like this:

          gql`
          query ($buildId: ID!) {
            builds {
              byId(buildId: $buildId) {
                platform
                artifacts {
                  buildUrl
                }
              }
            }
          }`, { buildId }

This feature is built in the GraphQL language to avoid piecing queries together from strings. It also ensures the queries are maximally reusable and is less error prone when it comes to things like correctly escaping the variables.

barthap

comment created time in a day

PullRequestReviewEvent
PullRequestReviewEvent

pull request commentexpo/eas-cli

[eas-cli] Extract GraphQL - proposal of QueryBuilder

I think defining all queries in one place is a good idea and we should do that. The website has a nice example of this at https://github.com/expo/universe/tree/a4916da65c5b56e0a12a45058f518d64d35be4e5/server/website/graphql. I think we can do something very similar and have static definitions of all queries in one place.

However, we should not add our own custom query builder library on top of the GraphQL client library we use. Developing and maintaining a separate layer of abstraction like this increases the cost of maintenance for us without any obvious gains. You mention you have evaluated existing libraries, but you haven't named the libraries or mentioned the exact problems you had with them.

Anyway, I'm in favor colocating queries, so a good next step would be to update this PR to only move queries to a single folder and leave the QueryBuilder class out of this PR. We would keep the existing approach of using urql to fetch data, but now with pre-defined queries. I can also help get the typegen integrated tomorrow so there's no need to write and maintain any type defs by hand.

In the future, please discuss any major architectural changes like this with me first before investing time to implement the change.

barthap

comment created time in 2 days

pull request commentexpo/eas-cli

[eas-cli] Extract GraphQL - proposal of QueryBuilder

I see this pull request adds a new abstraction QueryBuilder on top of the small @urql/core library that we started to use for GraphQL. Could you explain the idea behind QueryBuilder and why it's needed rather than using the functionality provided by urql directly?

For TypeScript types of GraphQL results, the plan was to use https://graphql-code-generator.com which supports urql with the @graphql-codegen/typescript-urql package. GraphQL Code Generator is also used to generate the TS types on thewww project, so I think it should work well for this project too. (Apologies for not having finished setting up it yet.)

barthap

comment created time in 2 days

startedawesome-selfhosted/awesome-selfhosted

started time in 4 days

push eventexpo/eas-cli

Ville Immonen

commit sha 943eb7087f4aa26cced7973a48a98d7243e93703

Add tsc build to test GH action (#35)

view details

push time in 5 days

delete branch expo/eas-cli

delete branch : @fson/tsc

delete time in 5 days

PR merged expo/eas-cli

Add tsc build to test GH action
+12 -4

0 comment

3 changed files

fson

pr closed time in 5 days

Pull request review commentexpo/eas-cli

Add tsc build to test GH action

     "@babel/preset-typescript": "^7.10.4",     "@expo/babel-preset-cli": "^0.2.17",     "@expo/oclif-dev-cli": "1.23.0-expo",+    "@types/color-string": "^1.5.0",

The problem was that we depend on it through @expo/config which wasn't upgraded to use the newer version yet. Fixed here: https://github.com/expo/expo-cli/pull/2810

fson

comment created time in 5 days

PullRequestReviewEvent

PR opened expo/eas-cli

Reviewers
Add tsc build to test GH action
+12 -4

0 comment

3 changed files

pr created time in 5 days

create barnchexpo/eas-cli

branch : @fson/tsc

created branch time in 5 days

PR opened expo/expo-cli

Reviewers
Upgrade @expo/configure-splash-screen version used in @expo/config

Update to 0.3.0. I need this in eas-cli because the type definition is broken in 0.2.1.

It seems that this version number doesn't get updated automatically during releases now that configure-splash-screen is in unlinked-packages, but maybe that's OK.

Not sure how to best test this change, but CHANGELOG didn't show anything alarming.

+5 -5

0 comment

2 changed files

pr created time in 5 days

create barnchexpo/expo-cli

branch : @fson/update-confsplscr

created branch time in 5 days

startedggoodman/json-schema-to-dts

started time in 8 days

push eventexpo/eas-cli

Ville Immonen

commit sha 7d4130d6bcca1ddfd2080644598a30aed7c40340

Add shellcheck

view details

push time in 9 days

PR opened expo/eas-cli

Add Bash installer

Background

https://www.notion.so/expo/Release-strategies-40bd1a2d58794696a2ecec7ea3f7d36f

To test you can run:

curl https://raw.githubusercontent.com/expo/eas-cli/%40fson/installer/bin/install.sh | bash

Next steps:

  • hosting for install.sh
  • add release script to CI
  • make sure autoupdates work
+255 -30

0 comment

7 changed files

pr created time in 9 days

push eventexpo/eas-cli

Ville Immonen

commit sha 21d4bef6997e2638f05871dd249bb827c80bd0e7

Add installer test on Ubuntu

view details

push time in 9 days

push eventexpo/eas-cli

Ville Immonen

commit sha 0984e6d3fe6354ccb2d51fc1e3d8e1a7ae826cd3

Only use sudo to link the executable, if necessary

view details

push time in 9 days

push eventexpo/eas-cli

Ville Immonen

commit sha 9dcc57834820624dc7ed09197f5784f7f197b7ef

Add comment and quotes

view details

push time in 9 days

push eventexpo/eas-cli

Ville Immonen

commit sha 81f178f791c0bd6d71076459cb00db1961d46b76

Add a bash installer for EAS CLI

view details

push time in 9 days

push eventexpo/eas-cli

Ville Immonen

commit sha 4d4837c135a9951986e58bc706703123f481aacf

Add a bash installer for EAS CLI

view details

push time in 9 days

push eventexpo/eas-cli

Ville Immonen

commit sha 769070bb5103923ae3411fd883260de10aee1c22

WIP

view details

push time in 9 days

push eventexpo/eas-cli

Ville Immonen

commit sha a81dbbd9c484d7641439f44d64c7b61e6c3a155e

WIP

view details

push time in 9 days

push eventexpo/eas-cli

Ville Immonen

commit sha 4ecd87e500f023c6199eaa1dc3558867080a0885

WIP

view details

push time in 9 days

push eventexpo/eas-cli

Ville Immonen

commit sha 439f407a9ed79a3dd41b5e2a940fd11749608231

WIP

view details

push time in 10 days

create barnchexpo/eas-cli

branch : @fson/installer

created branch time in 10 days

release expo/eas-cli

v0.0.0-alpha

released time in 10 days

Pull request review commentexpo/expo-cli

[pkcs12] new package for PKCS#12 utilities

+import crypto, { HexBase64Latin1Encoding, Utf8AsciiLatin1Encoding } from 'crypto';+import forge from 'node-forge';++/**+ * returns serial number as an uppercased hexidecimal string+ */+export function getFormattedSerialNumber(certificate: forge.pki.Certificate): string | null {+  const { serialNumber } = certificate;+  return serialNumber ? serialNumber.replace(/^0+/, '').toUpperCase() : null;+}++/**+ * Extracts a certificate from PKCS#12+ * This is assumed to be a conventional PKCS#12 where there is exactly one certificate and one key+ */+export function getX509Certificate(p12: forge.pkcs12.Pkcs12Pfx): forge.pki.Certificate {+  const certBagType = forge.pki.oids.certBag;+  const bags = p12.getBags({ bagType: certBagType })[certBagType];+  if (!bags || bags.length === 0) {+    throw new Error(`PKCS12: No certificates found`);+  }+  const certificate = bags[0].cert;+  if (!certificate) {+    throw new Error('PKCS12: bag is not a certificate');+  }+  return certificate;+}++/**+ * Extracts a certificate from PKCS#12+ * This is assumed to be a PKCS#12 in Keystore format where the friendlyName (alias) contains a PrivateKeyEntry+ * A PrivateKeyEntry contains exactly one certificate and one key+ * https://docs.oracle.com/javase/7/docs/api/java/security/KeyStore.PrivateKeyEntry.html+ */+export function getX509CertificateByFriendlyName(+  p12: forge.pkcs12.Pkcs12Pfx,+  friendlyName: string+): forge.pki.Certificate {+  const certBagType = forge.pki.oids.certBag;+  const bags = p12.getBags({ friendlyName, bagType: certBagType }).friendlyName;+  if (!bags || bags.length === 0) {+    throw new Error(`PKCS12: No certificates found under friendlyName: ${friendlyName}`);+  }+  const certificate = bags[0].cert;+  if (!certificate) {+    throw new Error('PKCS12: bag is not a certificate');+  }+  return certificate;+}++export function getPKCS12(

Decrypt?

quinlanj

comment created time in 15 days

PullRequestReviewEvent

startedkoalaman/shellcheck

started time in 16 days

startedHomebrew/install

started time in 16 days

startedchef/mixlib-install

started time in 18 days

created tagexpo/oclif-dev-cli

tagv1.23.0-expo

created time in 23 days

created tagexpo/oclif-dev-cli

tagv1.24.0-expo

created time in 23 days

push eventexpo/oclif-dev-cli

Ville Immonen

commit sha 166e58524cca24a7b5f5605b1c47ac8f9e83548a

Publish 1.24.0-expo

view details

push time in 23 days

push eventexpo/oclif-dev-cli

Ville Immonen

commit sha 39a2e87684c82aee8965f11c66206bb09bb71d36

Use local workspace dependencies during build Rewrite dependencies found in the workspace to use the `file:` protocol, so that instead of installing them from the npm registry, the local package is used.

view details

push time in 23 days

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentexpo/expo-cli

[cli] Add two-factor authentication to login

 export async function login(options: CommandOptions): Promise<User> {   } } -async function _usernamePasswordAuth(username?: string, password?: string): Promise<User> {+/**+ * Prompt for an OTP with the option to cancel the question by answering empty (pressing return key).+ */+async function _promptForOTPAsync(cancelBehavior: 'cancel' | 'menu'): Promise<string | null> {+  const enterMessage =+    cancelBehavior === 'cancel'+      ? `press ${log.chalk.bold('Enter')} to cancel`+      : `press ${log.chalk.bold('Enter')} for more options`;+  const otpQuestion: NewQuestion = {+    type: 'text',+    name: 'otp',+    message: `One-time password or backup code (${enterMessage}):`,+  };++  const { otp } = await promptNew(otpQuestion);+  if (!otp) {+    return null;+  }++  return otp;+}++/**+ * Prompt for user to choose a backup OTP method. If selected method is SMS, a request+ * for a new OTP will be sent to that method. Then, prompt for the OTP, and retry the user login.+ */+async function _promptForBackupOTPAsync(+  username: string,+  password: string,+  secondFactorDevices: SecondFactorDevice[]+): Promise<string | null> {+  const nonPrimarySecondFactorDevices = secondFactorDevices.filter(device => !device.is_primary);++  if (nonPrimarySecondFactorDevices.length === 0) {+    log.warn(+      'No other second-factor devices set up. Ensure you have set up and certified a backup device.'+    );+    process.exit(1);
    throw new CommandError('LOGIN_CANCELLED', 'No other second-factor devices set up. Ensure you have set up and certified a backup device.');
wschurman

comment created time in a month

Pull request review commentexpo/expo-cli

[cli] Add two-factor authentication to login

 export async function login(options: CommandOptions): Promise<User> {   } } -async function _usernamePasswordAuth(username?: string, password?: string): Promise<User> {+/**+ * Prompt for an OTP with the option to cancel the question by answering empty (pressing return key).+ */+async function _promptForOTPAsync(cancelBehavior: 'cancel' | 'menu'): Promise<string | null> {+  const enterMessage =+    cancelBehavior === 'cancel'+      ? `press ${log.chalk.bold('Enter')} to cancel`+      : `press ${log.chalk.bold('Enter')} for more options`;+  const otpQuestion: NewQuestion = {+    type: 'text',+    name: 'otp',+    message: `One-time password or backup code (${enterMessage}):`,+  };++  const { otp } = await promptNew(otpQuestion);+  if (!otp) {+    return null;+  }++  return otp;+}++/**+ * Prompt for user to choose a backup OTP method. If selected method is SMS, a request+ * for a new OTP will be sent to that method. Then, prompt for the OTP, and retry the user login.+ */+async function _promptForBackupOTPAsync(+  username: string,+  password: string,+  secondFactorDevices: SecondFactorDevice[]+): Promise<string | null> {+  const nonPrimarySecondFactorDevices = secondFactorDevices.filter(device => !device.is_primary);++  if (nonPrimarySecondFactorDevices.length === 0) {+    log.warn(+      'No other second-factor devices set up. Ensure you have set up and certified a backup device.'+    );+    process.exit(1);+  }++  const hasAuthenticatorSecondFactorDevice = nonPrimarySecondFactorDevices.find(+    device => device.method === UserSecondFactorDeviceMethod.AUTHENTICATOR+  );++  const smsNonPrimarySecondFactorDevices = nonPrimarySecondFactorDevices.filter(+    device => device.method === UserSecondFactorDeviceMethod.SMS+  );++  const authenticatorChoiceSentinel = -1;+  const cancelChoiceSentinel = -2;++  const deviceChoices = smsNonPrimarySecondFactorDevices.map((device, idx) => ({+    title: device.sms_phone_number!,+    value: idx,+  }));++  if (hasAuthenticatorSecondFactorDevice) {+    deviceChoices.push({+      title: 'Authenticator',+      value: authenticatorChoiceSentinel,+    });+  }++  deviceChoices.push({+    title: 'Cancel',+    value: cancelChoiceSentinel,+  });++  const question = {+    message: 'Select a second-factor device:',+    choices: deviceChoices,+  };++  const selectedValue = await selectAsync(question);+  if (selectedValue === cancelChoiceSentinel) {+    return null;+  } else if (selectedValue === authenticatorChoiceSentinel) {+    return await _promptForOTPAsync('cancel');+  }++  const device = smsNonPrimarySecondFactorDevices[selectedValue];++  const apiAnonymous = ApiV2.clientForUser();+  await apiAnonymous.postAsync('auth/send-sms-otp', {+    username,+    password,+    secondFactorDeviceID: device.id,+  });++  return await _promptForOTPAsync('cancel');+}++/**+ * Handle the special case error indicating that a second-factor is required for+ * authentication.+ *+ * There are three cases we need to handle:+ * 1. User's primary second-factor device was SMS, OTP was automatically sent by the server to that+ *    device already. In this case we should just prompt for the SMS OTP (or backup code), which the+ *    user should be receiving shortly. We should give the user a way to cancel and the prompt and move+ *    to case 3 below.+ * 2. User's primary second-factor device is authenticator. In this case we should prompt for authenticator+ *    OTP (or backup code) and also give the user a way to cancel and move to case 3 below.+ * 3. User doesn't have a primary device or doesn't have access to their primary device. In this case+ *    we should show a picker of the SMS devices that they can have an OTP code sent to, and when+ *    the user picks one we show a prompt for the sent OTP.+ */+export async function _retryUsernamePasswordAuthWithOTPAsync(+  username: string,+  password: string,+  metadata: {+    secondFactorDevices?: SecondFactorDevice[];+    smsAutomaticallySent?: boolean;+  }+): Promise<User> {+  const { secondFactorDevices, smsAutomaticallySent } = metadata;+  invariant(+    secondFactorDevices !== undefined && smsAutomaticallySent !== undefined,+    `Malformed OTP error metadata: ${metadata}`+  );++  const primaryDevice = secondFactorDevices.find(device => device.is_primary);+  let otp: string | null = null;++  if (smsAutomaticallySent) {+    invariant(+      primaryDevice,+      'OTP should only automatically be sent when there is a primary device'+    );+    log(+      `One-time password was sent to the phone number ending in ${primaryDevice.sms_phone_number}.`+    );+    otp = await _promptForOTPAsync('menu');+  }++  if (primaryDevice?.method === UserSecondFactorDeviceMethod.AUTHENTICATOR) {+    log('One-time password from authenticator required.');+    otp = await _promptForOTPAsync('menu');+  }++  // user bailed on case 1 or 2, wants to move to case 3+  if (!otp) {+    otp = await _promptForBackupOTPAsync(username, password, secondFactorDevices);+  }++  if (!otp) {+    log.warn('Cancelled login');+    process.exit(1);
    throw new CommandError('LOGIN_CANCELLED', 'Cancelled login');

I think we don't want to call process.exit(1) as it will unconditionally exit the process – killing the dev server if login is attempted and then cancelled. For example, if you click "Publish" in Expo Dev Tools web UI, this login prompt will be shown. Then if you decide to cancel login, the server should keep running and an error message gets printed in the console (this is the existing behavior if you pick "Cancel" in this prompt): https://github.com/expo/expo-cli/blob/31108dd5a643dc2868216bcb7c549c844ec72ed1/packages/expo-cli/src/accounts.ts#L33-L47

Cancelling the login may throw an error, which gets handled in the UI, but the dev server keeps running. I suggest using CommandError as it's an error that doesn't print a stack trace if it bubbles up to the top level error handler.

wschurman

comment created time in a month

PullRequestReviewEvent
PullRequestReviewEvent

pull request commentexpo/expo-cli

[cli] Use update-check for updates

Vercel no longer uses update-check in their own CLI: https://github.com/vercel/vercel/commit/843be9658c285fa73a9461d6fa4f9e2324293f03 🤔 I wonder if they're going to keep maintaining it.

EvanBacon

comment created time in a month

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentexpo/expo-cli

[xdl] Remove unused methods from Project module

 export type PublicationDetail = {  const VERSION = 2; +async function getSlugAsync({+  projectRoot,+  exp,+}: {+  projectRoot: string;+  exp: Pick<ExpoConfig, 'slug'>;+}): Promise<string> {+  if (exp.slug) {

This check (and the whole function?) now seems redundant, because at some point we made it so that a slug is always returned from getConfig: https://github.com/expo/expo-cli/blob/4aef8157645bed019ecc65d3ec216e59fedf6337/packages/config-types/src/index.ts#L17-L20

EvanBacon

comment created time in a month

PullRequestReviewEvent
PullRequestReviewEvent

push eventexpo/oclif-dev-cli

Ville Immonen

commit sha 22735d991fe0ca06a6142c87249b9def2ef09880

Publish 1.23.0-expo

view details

push time in a month

push eventexpo/oclif-dev-cli

Garrett Stevens

commit sha a0d78ca3b9484d424cca3e646327b1e50242bc6d

fix: find yarn.lock in a yarn workspace

view details

Ville Immonen

commit sha 4b1639e50cfeadb19ef190a483eae247972b0e70

fix: find yarn.lock in a yarn workspace (https://github.com/oclif/dev-cli/pull/156)

view details

push time in a month

pull request commentoclif/dev-cli

fix: find yarn.lock in a yarn workspace

This patch is needed to make @oclif/dev-cli work with Yarn workspaces. Without it, @oclif/dev-cli pack in a repo with Yarn workspaces doesn't find the yarn.lock file and throws this error:

    Error: ENOENT: no such file or directory, lstat 
    'npm-shrinkwrap.json'
    Code: ENOENT

Heroku CLI, which uses Yarn workspaces, seems to currently work around this by copying yarn.lock to the package subfolder (https://github.com/heroku/cli/blob/d130ded9a413d4a4996708173708cb2ecdc2702c/.circleci/config.yml#L169) but this is not for everyone as it might have unintended consequences and not at all obvious from the error that you should do something like this.

I hope this PR can be merged as it fixes the problem and makes pack work with Yarn workspaces out of the box!

garrettjstevens

comment created time in a month

delete branch expo/oclif-dev-cli

delete branch : master

delete time in a month

create barnchexpo/oclif-dev-cli

branch : main

created branch time in a month

Pull request review commentexpo/expo-cli

remove extra .gitignores

 node_modules *.tsbuildinfo .history +packages/*/build/++# Logs+logs+*.log++# Runtime data+pids+*.pid+*.seed++# Directory for instrumented libs generated by jscoverage/JSCover+lib-cov++# Coverage directory used by tools like istanbul coverage/++# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)+.grunt++# node-waf configuration+.lock-wscript++# Compiled binary addons (http://nodejs.org/api/addons.html)+build/Release

This could already be covered by packages/*/build/?

EvanBacon

comment created time in a month

PullRequestReviewEvent

Pull request review commentexpo/expo-cli

remove extra .gitignores

 node_modules *.tsbuildinfo .history +packages/*/build/++# Logs+logs+*.log++# Runtime data+pids+*.pid+*.seed++# Directory for instrumented libs generated by jscoverage/JSCover+lib-cov++# Coverage directory used by tools like istanbul coverage/++# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)+.grunt

I believe this can be removed as we're not using Grunt.

EvanBacon

comment created time in a month

PullRequestReviewEvent

Pull request review commentexpo/expo-cli

remove extra .gitignores

 node_modules *.tsbuildinfo .history +**/*/build++# Logs+logs

Does this have an effect on https://github.com/expo/expo-cli/tree/f88f72faa5845e12827d24534c8e171be97e92b7/packages/xdl/src/logs ? And does some package actually use this path to store logs?

EvanBacon

comment created time in a month

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentexpo/expo-cli

Improve JSON error formatting

 Command.prototype.asyncAction = function (asyncFn: Action, skipUpdateCheck: bool   }); }; +function getStringBetweenParens(value: string): string {+  const regExp = /\(([^)]+)\)/;+  const matches = regExp.exec(value);+  if (matches && matches?.length > 1) {+    return matches[1];+  }+  return value;+}++function focusLastPathComponent(value: string): string {

Can we put something there to fill the gap? E.g. Chrome Dev Tools shows (anonymous function) instead of the name, if there's no function name for the stack frame, I think.

EvanBacon

comment created time in a month

PullRequestReviewEvent

Pull request review commentexpo/expo-cli

Improve JSON error formatting

 Command.prototype.asyncAction = function (asyncFn: Action, skipUpdateCheck: bool   }); }; +function getStringBetweenParens(value: string): string {+  const regExp = /\(([^)]+)\)/;+  const matches = regExp.exec(value);+  if (matches && matches?.length > 1) {+    return matches[1];+  }+  return value;+}++function focusLastPathComponent(value: string): string {

93028954-d2d3d480-f617-11ea-822c-6ad25ce10493

Maybe the beginning of the path should be dimmed for lines that don't have a function name too? It seems that in this screenshot they're not?

EvanBacon

comment created time in a month

PullRequestReviewEvent

Pull request review commentexpo/expo-cli

Improve JSON error formatting

 async function validateAsync(     return NO_ISSUES;   } -  let exp, pkg;-  try {-    const config = getConfig(projectRoot, {-      strict: true,-      skipSDKVersionRequirement,-    });-    exp = config.exp;-    pkg = config.pkg;-    ProjectUtils.clearNotification(projectRoot, 'doctor-config-json-not-read');-  } catch (e) {-    ProjectUtils.logError(-      projectRoot,-      'expo',-      `Error: could not load config json at ${projectRoot}: ${e.toString()}`,-      'doctor-config-json-not-read'-    );-    return ERROR;-  }+  const { exp, pkg } = getConfig(projectRoot, {+    strict: true,+    skipSDKVersionRequirement,+  });++  ProjectUtils.clearNotification(projectRoot, 'doctor-config-json-not-read');

So this just throws now if config can't be read? Seems better than wrapping the original error with "could not load config json..."

But I wonder if throwing will cause an UnhandledPromiseRejectionWarning here: https://github.com/expo/expo-cli/blob/2c51e2f7b549ef6e2b3cb86f721959ad01ae6c5d/packages/xdl/src/project/ManifestHandler.ts#L120

EvanBacon

comment created time in a month

PullRequestReviewEvent

pull request commentexpo/expo-cli

Improve JSON error formatting

The stacktraces! 😍

These new stack traces are beautiful. If we want to make other stack traces we show (e.g. Metro/Webpack errors, device logs) look the same, can we reuse this code?

EvanBacon

comment created time in a month

Pull request review commentexpo/expo-cli

Generate types for Expo config

+/* tslint:disable */+/**+ * This file was automatically generated by json-schema-to-typescript.+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,+ * and run json-schema-to-typescript to regenerate this file.+ */++export interface ExpoConfig {+  /**+   * The name of your app as it appears both within Expo client and on your home screen as a standalone app.+   */+  name: string;+  /**+   * A short description of what your app is and why it is great.+   */+  description?: string;+  /**+   * The friendly URL name for publishing. For example, `myAppName` will refer to the `expo.io/@project-owner/myAppName` project.+   */+  slug: string;+  /**+   * The Expo account name of the team owner, only applicable if you are enrolled in Expo Developer Services. If not provided, defaults to the username of the current user.+   */+  owner?: string;+  /**+   * Defaults to `unlisted`. `unlisted` hides the project from search results. `hidden` restricts access to the project page to only the owner and other users that have been granted access. Valid values: `public`, `unlisted`, `hidden`.+   */+  privacy?: 'public' | 'unlisted' | 'hidden';+  /**+   * The Expo sdkVersion to run the project on. This should line up with the version specified in your package.json.+   */+  sdkVersion?: string;+  /**+   * **Note: Don't use this property unless you are sure what you're doing**+   *+   * The runtime version associated with this manifest for bare workflow projects. If provided, this must match the version set in Expo.plist or AndroidManifest.xml.+   */+  runtimeVersion?: string;+  /**+   * Your app version. In addition to this field, you'll also use `ios.buildNumber` and `android.versionCode` — read more about how to version your app [here](../../distribution/app-stores/#versioning-your-app). On iOS this corresponds to `CFBundleShortVersionString`, and on Android, this corresponds to `versionName`. The required format can be found [here](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring).+   */+  version?: string;+  /**+   * Platforms that your project explicitly supports. If not specified, it defaults to `["ios", "android"]`.+   */+  platforms?: ('android' | 'ios' | 'web')[];+  /**+   * If you would like to share the source code of your app on Github, enter the URL for the repository here and it will be linked to from your Expo project page.+   */+  githubUrl?: string;+  /**+   * Locks your app to a specific orientation with portrait or landscape. Defaults to no lock. Valid values: `default`, `portrait`, `landscape`+   */+  orientation?: ('default' | 'portrait' | 'landscape') | null;

So it can be one of 'default' | 'portrait' | 'landscape', orundefined, ornull` 🤔

EvanBacon

comment created time in a month

PullRequestReviewEvent
PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentexpo/expo-cli

[cli] Clean up TerminalUI

 const printHelp = (): void => {   log.nested(`${PLATFORM_TAG} Press ${b('?')} to show a list of all available commands.`); }; +const div = chalk.dim(`|`);+ const printUsage = async (projectDir: string, options: Pick<StartOptions, 'webOnly'> = {}) => {   const { dev } = await ProjectSettings.readAsync(projectDir);   const openDevToolsAtStartup = await UserSettings.getAsync('openDevToolsAtStartup', true);   const username = await UserManager.getCurrentUsernameAsync();   const devMode = dev ? 'development' : 'production';-  const androidInfo = `${b`a`} to run on ${u`A`}ndroid (${b`shift+a`} to select the device/emulator)`;-  const iosInfo =-    process.platform === 'darwin'-      ? `${b`i`} to run on ${u`i`}OS simulator (${b`shift+i`} to select the simulator model)`-      : '';-  const webInfo = `${b`w`} to run on ${u`w`}eb`;-  const platformInstructions = [androidInfo, iosInfo, webInfo]-    .filter(Boolean)-    .map(instructions => ` \u203A Press ${instructions}.`)-    .join('\n');-  log.nested(`-${platformInstructions}- \u203A Press ${b`c`} to show info on ${u`c`}onnecting new devices.- \u203A Press ${b`d`} to open DevTools in the default web browser.- \u203A Press ${b`shift-d`} to ${-    openDevToolsAtStartup ? 'disable' : 'enable'-  } automatically opening ${u`D`}evTools at startup.${-    options.webOnly ? '' : `\n \u203A Press ${b`e`} to send an app link with ${u`e`}mail.`-  }- \u203A Press ${b`p`} to toggle ${u`p`}roduction mode. (current mode: ${i(devMode)})- \u203A Press ${b`r`} to ${u`r`}estart bundler, or ${b`shift-r`} to restart and clear cache.- \u203A Press ${b`o`} to ${u`o`}pen the project in your editor.- \u203A Press ${b`s`} to ${u`s`}ign ${-    username ? `out. (Signed in as ${i('@' + username)}.)` : 'in.'-  }-`);+  const currentAuth = `@${username}`;+  const currentToggle = openDevToolsAtStartup ? 'disabled' : 'enabled';++  const isMac = process.platform === 'darwin';++  const ui = [+    [],+    ['a', `open Android`],+    ['shift+a', `select a device or emulator`],

I wonder if we could use something like ⇧+a here? Although I guess some fonts might not have this character.

EvanBacon

comment created time in 2 months

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentexpo/expo-cli

[xdl] delete deprecated Exp code

 export async function saveRecentExpRootAsync(root: string) {   return await recentExpsJsonFile.writeAsync(recentExps.slice(0, 100)); } -type PublishInfo = {-  args: {-    username: string;-    remoteUsername: string;-    remotePackageName: string;-    remoteFullPackageName: string;-    sdkVersion: string;-    iosBundleIdentifier?: string | null;-    androidPackage?: string | null;-  };-};--// TODO: remove / change, no longer publishInfo, this is just used for signing-/** @deprecated use getConfig from @expo/config */-export async function getPublishInfoAsync(root: string): Promise<PublishInfo> {

💯

EvanBacon

comment created time in 2 months

PullRequestReviewEvent
PullRequestReviewEvent
more