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
ClojureScript live preview.
fson/coffeescript-style-guide 2
Best-practices and coding conventions for the CoffeeScript programming language
Chosen is a library for making long, unwieldy select boxes more friendly.
fson/ad 0
Making Active Directory jQuery-easy
An open source mobile and web app that lets users share photos similar to Instagram
:rocket: A fully-featured caching GraphQL client for any server or UI framework
push eventexpo/eas-cli
commit sha d468c29f1cc4336a1824039a52934ad211dc3c19
Add error message on failure
push time in a day
Pull request review commentexpo/eas-cli
+#!/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.
comment created time in a day
Pull request review commentexpo/eas-cli
+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 :)
comment created time in a day
Pull request review commentexpo/eas-cli
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.
comment created time in a day
push eventexpo/eas-cli
commit sha ea1d8d21273f7bb3a4eff297a05fb41f2b1ea2e1
Remove incorrect code paths from readme
push time in a day
push eventexpo/eas-cli
commit sha ffb9b5840c4a760426821905877928329586319f
Only show the sudo warning if not already root
push time in a day
push eventexpo/eas-cli
commit sha 1825085f9987af6eff9ebe596aa6b7764e2f6bba
Update bin/install.sh Co-authored-by: James Ide <[email protected]>
push time in a day
push eventexpo/eas-cli
commit sha d3dbd4ecfc59668aa716ed4897308d97e80c0c2d
Update bin/install.sh Co-authored-by: James Ide <[email protected]>
push time in a day
Pull request review commentexpo/eas-cli
+#!/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
comment created time in a day
push eventexpo/eas-cli
commit sha 79a9fe7b356822991cac4f7f51e88ce7a8e4c0cf
Test on Ubuntu and macOS
push time in a day
Pull request review commentexpo/eas-cli
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
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 { 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.
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 { 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.
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.
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.
comment created time in a day
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.
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.)
comment created time in 2 days
startedawesome-selfhosted/awesome-selfhosted
started time in 4 days
push eventexpo/eas-cli
commit sha 943eb7087f4aa26cced7973a48a98d7243e93703
Add tsc build to test GH action (#35)
push time in 5 days
PR merged expo/eas-cli
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
comment created time in 5 days
PR opened expo/eas-cli
pr created time in 5 days
PR opened expo/expo-cli
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.
pr created time in 5 days
startedggoodman/json-schema-to-dts
started time in 8 days
push eventexpo/eas-cli
commit sha 7d4130d6bcca1ddfd2080644598a30aed7c40340
Add shellcheck
push time in 9 days
PR opened expo/eas-cli
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
pr created time in 9 days
push eventexpo/eas-cli
commit sha 21d4bef6997e2638f05871dd249bb827c80bd0e7
Add installer test on Ubuntu
push time in 9 days
push eventexpo/eas-cli
commit sha 0984e6d3fe6354ccb2d51fc1e3d8e1a7ae826cd3
Only use sudo to link the executable, if necessary
push time in 9 days
push eventexpo/eas-cli
commit sha 9dcc57834820624dc7ed09197f5784f7f197b7ef
Add comment and quotes
push time in 9 days
push eventexpo/eas-cli
commit sha 81f178f791c0bd6d71076459cb00db1961d46b76
Add a bash installer for EAS CLI
push time in 9 days
push eventexpo/eas-cli
commit sha 4d4837c135a9951986e58bc706703123f481aacf
Add a bash installer for EAS CLI
push time in 9 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?
comment created time in 15 days
startedkoalaman/shellcheck
started time in 16 days
startedHomebrew/install
started time in 16 days
startedchef/mixlib-install
started time in 18 days
push eventexpo/oclif-dev-cli
commit sha 166e58524cca24a7b5f5605b1c47ac8f9e83548a
Publish 1.24.0-expo
push time in 23 days
push eventexpo/oclif-dev-cli
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.
push time in 23 days
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.');
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.
comment created time in a month
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.
comment created time in a month
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
comment created time in a month
push eventexpo/oclif-dev-cli
commit sha 22735d991fe0ca06a6142c87249b9def2ef09880
Publish 1.23.0-expo
push time in a month
push eventexpo/oclif-dev-cli
commit sha a0d78ca3b9484d424cca3e646327b1e50242bc6d
fix: find yarn.lock in a yarn workspace
commit sha 4b1639e50cfeadb19ef190a483eae247972b0e70
fix: find yarn.lock in a yarn workspace (https://github.com/oclif/dev-cli/pull/156)
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!
comment created time in a month
Pull request review commentexpo/expo-cli
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/?
comment created time in a month
Pull request review commentexpo/expo-cli
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.
comment created time in a month
Pull request review commentexpo/expo-cli
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?
comment created time in a month
Pull request review commentexpo/expo-cli
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.
comment created time in a month
Pull request review commentexpo/expo-cli
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 {

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?
comment created time in a month
Pull request review commentexpo/expo-cli
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
comment created time in a month
pull request commentexpo/expo-cli
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?
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` 🤔
comment created time in a month
Pull request review commentexpo/expo-cli
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.
comment created time in 2 months
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> {
💯
comment created time in 2 months