mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
Compare commits
40
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9dcc69d3d0 | ||
|
|
a58c4817f5 | ||
|
|
abf3904bae | ||
|
|
4ebdae0ca2 | ||
|
|
8c12d6512a | ||
|
|
c71cf4df5b | ||
|
|
f76152fef5 | ||
|
|
f19801b668 | ||
|
|
7e0fefa6b0 | ||
|
|
7f2749fc2d | ||
|
|
0ec40f02b4 | ||
|
|
5c23d16412 | ||
|
|
39e014b188 | ||
|
|
0c32aa3a39 | ||
|
|
59ac8ae77a | ||
|
|
3e13df470e | ||
|
|
83877f6285 | ||
|
|
56018338e7 | ||
|
|
0fdf2dd44a | ||
|
|
0dcd6d465a | ||
|
|
d5da4f4167 | ||
|
|
d0b9281bb2 | ||
|
|
77e04a3bad | ||
|
|
92ff6a0da5 | ||
|
|
25d945b63f | ||
|
|
37165428d1 | ||
|
|
50b9be975e | ||
|
|
2655482f74 | ||
|
|
dc103acf06 | ||
|
|
b87bc5bf8f | ||
|
|
1604b7bc20 | ||
|
|
13a6356fca | ||
|
|
0823a9d675 | ||
|
|
772d4dd654 | ||
|
|
e8da383de4 | ||
|
|
d08f9cfdcb | ||
|
|
ac6569aa58 | ||
|
|
960ce7dbf5 | ||
|
|
029bbdd007 | ||
|
|
7061a6743a |
No files matched your search
@@ -1,9 +1,14 @@
|
|||||||
**/build/**
|
**/build/**
|
||||||
**/dist/**
|
**/dist/**
|
||||||
|
**/coverage/**
|
||||||
|
**/fixtures/**
|
||||||
!packages/eas-cli/src/commands/build/**
|
!packages/eas-cli/src/commands/build/**
|
||||||
!packages/eas-cli/src/build/**
|
!packages/eas-cli/src/build/**
|
||||||
|
|
||||||
packages/eas-cli/src/graphql/generated.ts
|
packages/eas-cli/src/graphql/generated.ts
|
||||||
|
packages/eas-cli/schema/**
|
||||||
|
packages/eas-cli/graphql.schema.json
|
||||||
|
packages/build-tools/schema.graphql
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
ts-declarations
|
ts-declarations
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
extends: ['universe/node'],
|
|
||||||
plugins: ['async-protect', 'node'],
|
|
||||||
rules: {
|
|
||||||
'no-console': 'warn',
|
|
||||||
'no-constant-condition': ['warn', { checkLoops: false }],
|
|
||||||
'sort-imports': [
|
|
||||||
'warn',
|
|
||||||
{
|
|
||||||
ignoreDeclarationSort: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
curly: 'warn',
|
|
||||||
'import/no-cycle': 'error',
|
|
||||||
'import/no-extraneous-dependencies': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
devDependencies: [
|
|
||||||
'**/jest/**/*',
|
|
||||||
'**/__tests__/**/*',
|
|
||||||
'**/__mocks__/**/*',
|
|
||||||
'**/__integration-tests__/**/*',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'import/no-relative-packages': 'error',
|
|
||||||
'no-restricted-imports': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
paths: [
|
|
||||||
{
|
|
||||||
name: 'lodash',
|
|
||||||
message: "Don't use lodash, it's heavy!",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'no-underscore-dangle': ['error', { allow: ['__typename'] }],
|
|
||||||
'async-protect/async-suffix': 'error',
|
|
||||||
'node/no-sync': 'error',
|
|
||||||
},
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ['**/__tests__/**/*.ts', '**/__mocks__/**/*.ts'],
|
|
||||||
rules: {
|
|
||||||
'async-protect/async-suffix': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
- "v*" # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||||
|
|
||||||
name: Release workflow
|
name: Release workflow
|
||||||
|
|
||||||
@@ -22,149 +22,21 @@ jobs:
|
|||||||
tag_name: ${{ github.ref }}
|
tag_name: ${{ github.ref }}
|
||||||
release_name: ${{ github.ref }}
|
release_name: ${{ github.ref }}
|
||||||
draft: true
|
draft: true
|
||||||
build-linux:
|
|
||||||
name: Build for Linux
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: release
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: ./.github/actions/setup-mise
|
|
||||||
- run: yarn install --frozen-lockfile --check-files
|
|
||||||
- run: yarn build
|
|
||||||
- name: Ensure GraphQL schema and generated code is up-to-date
|
|
||||||
working-directory: ${{ github.workspace }}/packages/eas-cli
|
|
||||||
run: |
|
|
||||||
yarn verify-graphql-code
|
|
||||||
- name: Build tarballs
|
|
||||||
working-directory: ${{ github.workspace }}/packages/eas-cli
|
|
||||||
run: |
|
|
||||||
yarn pretarball-ci
|
|
||||||
yarn oclif pack:tarballs --no-xz --targets linux-x64,linux-arm
|
|
||||||
- id: x64
|
|
||||||
run: echo "filename=$(ls eas-*-x64.tar.gz)" >> $GITHUB_OUTPUT
|
|
||||||
working-directory: ${{ github.workspace }}/packages/eas-cli/dist
|
|
||||||
- id: arm
|
|
||||||
run: echo "filename=$(ls eas-*-arm.tar.gz)" >> $GITHUB_OUTPUT
|
|
||||||
working-directory: ${{ github.workspace }}/packages/eas-cli/dist
|
|
||||||
- name: Upload linux-x64 tarball
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ needs.release.outputs.upload_url }}
|
|
||||||
asset_path: packages/eas-cli/dist/${{ steps.x64.outputs.filename }}
|
|
||||||
asset_name: ${{ steps.x64.outputs.filename }}
|
|
||||||
asset_content_type: application/gzip
|
|
||||||
- name: Upload linux-arm tarball
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ needs.release.outputs.upload_url }}
|
|
||||||
asset_path: packages/eas-cli/dist/${{ steps.arm.outputs.filename }}
|
|
||||||
asset_name: ${{ steps.arm.outputs.filename }}
|
|
||||||
asset_content_type: application/gzip
|
|
||||||
build-mac:
|
|
||||||
name: Build for macOS
|
|
||||||
# Use macOS to get the correct platform specific dependencies
|
|
||||||
runs-on: macos-latest
|
|
||||||
needs: release
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: ./.github/actions/setup-mise
|
|
||||||
- run: brew install python-setuptools
|
|
||||||
env:
|
|
||||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
|
||||||
- run: yarn install --frozen-lockfile --check-files
|
|
||||||
- run: yarn build
|
|
||||||
- name: Build tarballs
|
|
||||||
working-directory: ${{ github.workspace }}/packages/eas-cli
|
|
||||||
run: |
|
|
||||||
yarn pretarball-ci
|
|
||||||
yarn oclif pack:tarballs --no-xz --targets darwin-x64,darwin-arm64
|
|
||||||
- id: x64
|
|
||||||
run: echo "filename=$(ls eas-*-x64.tar.gz)" >> $GITHUB_OUTPUT
|
|
||||||
working-directory: ${{ github.workspace }}/packages/eas-cli/dist
|
|
||||||
- id: arm64
|
|
||||||
run: echo "filename=$(ls eas-*-arm64.tar.gz)" >> $GITHUB_OUTPUT
|
|
||||||
working-directory: ${{ github.workspace }}/packages/eas-cli/dist
|
|
||||||
- name: Upload darwin-x64 tarball
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ needs.release.outputs.upload_url }}
|
|
||||||
asset_path: packages/eas-cli/dist/${{ steps.x64.outputs.filename }}
|
|
||||||
asset_name: ${{ steps.x64.outputs.filename }}
|
|
||||||
asset_content_type: application/gzip
|
|
||||||
- name: Upload darwin-arm64 tarball
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ needs.release.outputs.upload_url }}
|
|
||||||
asset_path: packages/eas-cli/dist/${{ steps.arm64.outputs.filename }}
|
|
||||||
asset_name: ${{ steps.arm64.outputs.filename }}
|
|
||||||
asset_content_type: application/gzip
|
|
||||||
build-windows:
|
|
||||||
name: Build for Windows
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: release
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: ./.github/actions/setup-mise
|
|
||||||
- run: yarn install --frozen-lockfile --check-files
|
|
||||||
- run: sudo apt-get install nsis
|
|
||||||
- run: yarn build
|
|
||||||
- run: |
|
|
||||||
yarn pretarball-ci
|
|
||||||
yarn oclif pack:win
|
|
||||||
working-directory: ${{ github.workspace }}/packages/eas-cli
|
|
||||||
- id: x64
|
|
||||||
run: echo "filename=$(ls eas-*-x64.exe)" >> $GITHUB_OUTPUT
|
|
||||||
working-directory: ${{ github.workspace }}/packages/eas-cli/dist/win32
|
|
||||||
- id: x86
|
|
||||||
run: echo "filename=$(ls eas-*-x86.exe)" >> $GITHUB_OUTPUT
|
|
||||||
working-directory: ${{ github.workspace }}/packages/eas-cli/dist/win32
|
|
||||||
- name: Upload x64 installer
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ needs.release.outputs.upload_url }}
|
|
||||||
asset_path: packages/eas-cli/dist/win32/${{ steps.x64.outputs.filename }}
|
|
||||||
asset_name: ${{ steps.x64.outputs.filename }}
|
|
||||||
asset_content_type: application/gzip
|
|
||||||
- name: Upload x86 installer
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ needs.release.outputs.upload_url }}
|
|
||||||
asset_path: packages/eas-cli/dist/win32/${{ steps.x86.outputs.filename }}
|
|
||||||
asset_name: ${{ steps.x86.outputs.filename }}
|
|
||||||
asset_content_type: application/gzip
|
|
||||||
publish-to-npm:
|
publish-to-npm:
|
||||||
name: Publish to npm
|
name: Publish to npm
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build-linux, build-mac, build-windows]
|
needs: release
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/setup-mise
|
- uses: ./.github/actions/setup-mise
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn install --frozen-lockfile --check-files
|
run: yarn install --immutable
|
||||||
- name: Build
|
- name: Build
|
||||||
run: yarn build
|
run: yarn build
|
||||||
- name: Publish packages to npm
|
- name: Publish packages to npm
|
||||||
run: yarn lerna publish from-package --yes --no-verify-access
|
run: yarn run -T lerna publish from-package --yes --no-verify-access
|
||||||
- name: Add latest-eas-build-staging tag
|
- name: Add latest-eas-build-staging tag
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
||||||
@@ -192,12 +64,12 @@ jobs:
|
|||||||
- uses: ./.github/actions/setup-mise
|
- uses: ./.github/actions/setup-mise
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
working-directory: ./scripts
|
working-directory: ./scripts
|
||||||
run: yarn install --frozen-lockfile --check-files
|
run: yarn install --immutable
|
||||||
- name: Resolve version
|
- name: Resolve version
|
||||||
run: echo "EAS_CLI_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
run: echo "EAS_CLI_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||||
- name: Update changelog
|
- name: Update changelog
|
||||||
working-directory: ./scripts
|
working-directory: ./scripts
|
||||||
run: yarn --silent release-changelog $EAS_CLI_VERSION > /tmp/current-changelog
|
run: yarn release-changelog $EAS_CLI_VERSION > /tmp/current-changelog
|
||||||
- name: Publish release
|
- name: Publish release
|
||||||
run: gh release edit v$EAS_CLI_VERSION --draft=false -F /tmp/current-changelog
|
run: gh release edit v$EAS_CLI_VERSION --draft=false -F /tmp/current-changelog
|
||||||
env:
|
env:
|
||||||
@@ -224,8 +96,8 @@ jobs:
|
|||||||
SLACK_CHANNEL: eas-cli
|
SLACK_CHANNEL: eas-cli
|
||||||
SLACK_COLOR: ${{ job.status }}
|
SLACK_COLOR: ${{ job.status }}
|
||||||
SLACK_ICON: https://github.com/expo-bot.png?size=48
|
SLACK_ICON: https://github.com/expo-bot.png?size=48
|
||||||
SLACK_TITLE: 'Released eas-cli@${{ env.EAS_CLI_VERSION }}'
|
SLACK_TITLE: "Released eas-cli@${{ env.EAS_CLI_VERSION }}"
|
||||||
SLACK_MESSAGE: ${{ env.SLACK_CHANGELOG }}
|
SLACK_MESSAGE: ${{ env.SLACK_CHANGELOG }}
|
||||||
SLACK_USERNAME: 'EAS CLI CI'
|
SLACK_USERNAME: "EAS CLI CI"
|
||||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||||
SLACK_FOOTER: ''
|
SLACK_FOOTER: ""
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
name: Size
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [synchronize, opened]
|
|
||||||
concurrency:
|
|
||||||
group: size-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
|
||||||
compare:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: ./.github/actions/setup-mise
|
|
||||||
- uses: preactjs/compressed-size-action@v2
|
|
||||||
with:
|
|
||||||
build-script: 'build:tarballs:linux'
|
|
||||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
|
||||||
pattern: './packages/eas-cli/dist/*.tar.gz'
|
|
||||||
compression: 'none'
|
|
||||||
strip-hash: "v\\d+.\\d+.\\d+-\\w+-"
|
|
||||||
clean-script: 'clean'
|
|
||||||
@@ -20,6 +20,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Setup tools
|
- name: Setup tools
|
||||||
uses: ./.github/actions/setup-mise
|
uses: ./.github/actions/setup-mise
|
||||||
- run: yarn install --frozen-lockfile
|
- run: yarn install --immutable
|
||||||
- run: yarn verify-graphql-code
|
- run: yarn verify-graphql-code
|
||||||
working-directory: packages/eas-cli
|
working-directory: packages/eas-cli
|
||||||
@@ -24,5 +24,5 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Setup tools
|
- name: Setup tools
|
||||||
uses: ./.github/actions/setup-mise
|
uses: ./.github/actions/setup-mise
|
||||||
- run: yarn install --frozen-lockfile --check-files
|
- run: yarn install --immutable
|
||||||
- run: yarn typecheck
|
- run: yarn typecheck
|
||||||
+20
-11
@@ -22,11 +22,14 @@ jobs:
|
|||||||
name: Test with Node ${{ matrix.node }}
|
name: Test with Node ${{ matrix.node }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Setup node
|
- name: Setup tools
|
||||||
uses: actions/setup-node@v2
|
uses: ./.github/actions/setup-mise
|
||||||
with:
|
env:
|
||||||
node-version: ${{ matrix.node }}
|
MISE_NODE_VERSION: ${{ matrix.node }}
|
||||||
- run: yarn install --frozen-lockfile --check-files
|
- name: Install dependencies
|
||||||
|
run: yarn install --immutable
|
||||||
|
env:
|
||||||
|
YARN_ENABLE_HARDENED_MODE: ${{ matrix.coverage == true && '1' || '0' }}
|
||||||
- run: yarn typecheck
|
- run: yarn typecheck
|
||||||
- run: |
|
- run: |
|
||||||
git config --global user.email "[email protected]"
|
git config --global user.email "[email protected]"
|
||||||
@@ -38,31 +41,37 @@ jobs:
|
|||||||
- name: Run tests (affected)
|
- name: Run tests (affected)
|
||||||
# PRs: only run tests for changed packages + dependents.
|
# PRs: only run tests for changed packages + dependents.
|
||||||
if: ${{ !matrix.coverage && github.event_name == 'pull_request' }}
|
if: ${{ !matrix.coverage && github.event_name == 'pull_request' }}
|
||||||
run: yarn lerna run test --since ${{ github.event.pull_request.base.sha }} --include-dependents
|
run: yarn run -T lerna run test --since ${{ github.event.pull_request.base.sha }} --include-dependents
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=8192
|
NODE_OPTIONS: --max-old-space-size=8192
|
||||||
- name: Run tests (all)
|
- name: Run tests (all)
|
||||||
# Pushes to main: run all package tests.
|
# Pushes to main: run all package tests.
|
||||||
if: ${{ !matrix.coverage && github.event_name == 'push' }}
|
if: ${{ !matrix.coverage && github.event_name == 'push' }}
|
||||||
run: yarn lerna run test
|
run: yarn run -T lerna run test
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=8192
|
NODE_OPTIONS: --max-old-space-size=8192
|
||||||
- name: Run tests (coverage)
|
- name: Run tests (coverage)
|
||||||
# Coverage on PRs and main; per-package reports are uploaded to Codecov.
|
# Coverage on PRs and main; per-package reports are uploaded to Codecov.
|
||||||
if: ${{ matrix.coverage }}
|
if: ${{ matrix.coverage }}
|
||||||
run: yarn lerna run test -- -- --coverage
|
run: yarn run -T lerna run test -- --coverage
|
||||||
env:
|
env:
|
||||||
# Oclif seems to cause OOM errors when testing with coverage enabled.
|
# Oclif seems to cause OOM errors when testing with coverage enabled.
|
||||||
# Remove this whenever we solved those OOM errors.
|
# Remove this whenever we solved those OOM errors.
|
||||||
NODE_OPTIONS: --max-old-space-size=8192
|
NODE_OPTIONS: --max-old-space-size=8192
|
||||||
- run: yarn lint --max-warnings=0
|
- run: yarn lint
|
||||||
- run: yarn install --frozen-lockfile --check-files
|
- run: yarn fmt:check
|
||||||
|
- name: Install scripts dependencies
|
||||||
|
if: ${{ matrix.coverage }}
|
||||||
|
run: yarn install --immutable
|
||||||
working-directory: ./scripts
|
working-directory: ./scripts
|
||||||
|
env:
|
||||||
|
YARN_ENABLE_HARDENED_MODE: ${{ matrix.coverage == true && '1' || '0' }}
|
||||||
- uses: codecov/codecov-action@v1
|
- uses: codecov/codecov-action@v1
|
||||||
if: ${{ matrix.coverage }}
|
if: ${{ matrix.coverage }}
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
- run: yarn lint-changelog
|
- if: ${{ matrix.coverage }}
|
||||||
|
run: yarn lint-changelog
|
||||||
|
|
||||||
notify-slack:
|
notify-slack:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
git checkout main
|
git checkout main
|
||||||
git config --global user.email "[email protected]"
|
git config --global user.email "[email protected]"
|
||||||
git config --global user.name "Expo CI"
|
git config --global user.name "Expo CI"
|
||||||
yarn install --frozen-lockfile --check-files
|
yarn install --immutable
|
||||||
yarn build
|
yarn build
|
||||||
if [[ "$INPUT_VERSION" == "" ]]; then
|
if [[ "$INPUT_VERSION" == "" ]]; then
|
||||||
echo "Releasing with default version"
|
echo "Releasing with default version"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: ./.github/actions/setup-mise
|
- uses: ./.github/actions/setup-mise
|
||||||
|
|
||||||
- run: yarn install --frozen-lockfile --check-files
|
- run: yarn install --immutable
|
||||||
|
|
||||||
- run: yarn typecheck
|
- run: yarn typecheck
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ dist
|
|||||||
|
|
||||||
# Node
|
# Node
|
||||||
node_modules
|
node_modules
|
||||||
|
**/.yarn/install-state.gz
|
||||||
*-debug.log
|
*-debug.log
|
||||||
*-error.log
|
*-error.log
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 100,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"bracketSameLine": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"ignorePatterns": [
|
||||||
|
".github",
|
||||||
|
"lerna.json",
|
||||||
|
"**/build/**",
|
||||||
|
"**/dist/**",
|
||||||
|
"**/coverage/**",
|
||||||
|
"**/fixtures/**",
|
||||||
|
"packages/eas-cli/README.md",
|
||||||
|
"packages/eas-cli/schema/**",
|
||||||
|
"packages/eas-cli/graphql.schema.json",
|
||||||
|
"packages/build-tools/schema.graphql"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||||
|
"categories": {
|
||||||
|
"correctness": "off",
|
||||||
|
"suspicious": "off",
|
||||||
|
"pedantic": "off",
|
||||||
|
"perf": "off",
|
||||||
|
"restriction": "off",
|
||||||
|
"style": "off",
|
||||||
|
"nursery": "off"
|
||||||
|
},
|
||||||
|
"ignorePatterns": [
|
||||||
|
"**/build/**",
|
||||||
|
"**/dist/**",
|
||||||
|
"**/coverage/**",
|
||||||
|
"**/fixtures/**",
|
||||||
|
"**/__fixtures__/**",
|
||||||
|
".github/**",
|
||||||
|
"packages/eas-cli/schema/**",
|
||||||
|
"packages/eas-cli/graphql.schema.json",
|
||||||
|
"packages/build-tools/schema.graphql"
|
||||||
|
],
|
||||||
|
"plugins": ["import", "node", "typescript"],
|
||||||
|
"jsPlugins": [
|
||||||
|
{
|
||||||
|
"name": "async-protect",
|
||||||
|
"specifier": "eslint-plugin-async-protect"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"no-console": "warn",
|
||||||
|
"no-constant-condition": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"checkLoops": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort-imports": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"ignoreDeclarationSort": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"curly": "warn",
|
||||||
|
"default": "off",
|
||||||
|
"named": "off",
|
||||||
|
"namespace": "off",
|
||||||
|
"no-cycle": "error",
|
||||||
|
"no-restricted-imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"name": "lodash",
|
||||||
|
"message": "Don't use lodash, it's heavy!"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-underscore-dangle": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"allow": ["__typename"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"async-protect/async-suffix": "error",
|
||||||
|
"node/no-sync": "error"
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["**/__tests__/**/*.ts", "**/__mocks__/**/*.ts"],
|
||||||
|
"rules": {
|
||||||
|
"async-protect/async-suffix": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"printWidth": 100,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"singleQuote": true,
|
|
||||||
"bracketSameLine": true,
|
|
||||||
"trailingComma": "es5",
|
|
||||||
"arrowParens": "avoid",
|
|
||||||
"endOfLine": "auto"
|
|
||||||
}
|
|
||||||
Vendored
+1
-2
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"eslint.enable": true,
|
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**/node_modules": true,
|
"**/node_modules": true,
|
||||||
"**/bower_components": true,
|
"**/bower_components": true,
|
||||||
@@ -7,4 +6,4 @@
|
|||||||
"**/src/**/build/**": false,
|
"**/src/**/build/**": false,
|
||||||
"**/src/build/**": false,
|
"**/src/build/**": false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
defaultSemverRangePrefix: ''
|
||||||
|
nodeLinker: node-modules
|
||||||
@@ -3,4 +3,3 @@
|
|||||||
When working with this repository follow instructions from CLAUDE.md.
|
When working with this repository follow instructions from CLAUDE.md.
|
||||||
|
|
||||||
- [./CLAUDE.md](./CLAUDE.md)
|
- [./CLAUDE.md](./CLAUDE.md)
|
||||||
|
|
||||||
+29
-1
@@ -6,10 +6,39 @@ This is the log of notable changes to EAS CLI and related packages.
|
|||||||
|
|
||||||
### 🛠 Breaking changes
|
### 🛠 Breaking changes
|
||||||
|
|
||||||
|
- Require --environment flag for update commands when SDK >= 55. ([#3398](https://github.com/expo/eas-cli/pull/3398) by [@douglowder](https://github.com/douglowder))
|
||||||
|
|
||||||
|
### 🎉 New features
|
||||||
|
|
||||||
|
- Add `eas observe:metrics` and `eas observe:events` commands for monitoring app performance metrics. ([#3401](https://github.com/expo/eas-cli/pull/3401) by [@ubax](https://github.com/ubax))
|
||||||
|
|
||||||
|
### 🐛 Bug fixes
|
||||||
|
|
||||||
|
### 🧹 Chores
|
||||||
|
|
||||||
|
## [18.0.4](https://github.com/expo/eas-cli/releases/tag/v18.0.4) - 2026-02-23
|
||||||
|
|
||||||
|
### 🐛 Bug fixes
|
||||||
|
|
||||||
|
- Revert authorization code flow with PKCE for browser-based login. ([#3422](https://github.com/expo/eas-cli/pull/3422) by [@byronkarlen](https://github.com/byronkarlen))
|
||||||
|
|
||||||
|
## [18.0.3](https://github.com/expo/eas-cli/releases/tag/v18.0.3) - 2026-02-20
|
||||||
|
|
||||||
|
### 🎉 New features
|
||||||
|
|
||||||
|
- Use authorization code flow with PKCE for browser-based login. ([#3398](https://github.com/expo/eas-cli/pull/3398) by [@byronkarlen](https://github.com/byronkarlen))
|
||||||
|
|
||||||
|
## [18.0.1](https://github.com/expo/eas-cli/releases/tag/v18.0.1) - 2026-02-12
|
||||||
|
|
||||||
|
### 🛠 Breaking changes
|
||||||
|
|
||||||
- Drop support for Node 18 and Node 19. Require Node 20+. ([#3343](https://github.com/expo/eas-cli/pull/3343) by [@sjchmiela](https://github.com/sjchmiela))
|
- Drop support for Node 18 and Node 19. Require Node 20+. ([#3343](https://github.com/expo/eas-cli/pull/3343) by [@sjchmiela](https://github.com/sjchmiela))
|
||||||
|
|
||||||
### 🎉 New features
|
### 🎉 New features
|
||||||
|
|
||||||
|
- Add `--browser` flag to `eas login` for browser-based authentication. ([#3312](https://github.com/expo/eas-cli/pull/3312) by [@byronkarlen](https://github.com/byronkarlen))
|
||||||
|
|
||||||
|
### 🐛 Bug fixes
|
||||||
|
|
||||||
- Hide progress bar in build credits warning when usage reaches 100%. ([#3371](https://github.com/expo/eas-cli/pull/3371) by [@mackenco](https://github.com/mackenco))
|
- Hide progress bar in build credits warning when usage reaches 100%. ([#3371](https://github.com/expo/eas-cli/pull/3371) by [@mackenco](https://github.com/mackenco))
|
||||||
|
|
||||||
@@ -21,7 +50,6 @@ This is the log of notable changes to EAS CLI and related packages.
|
|||||||
|
|
||||||
### 🎉 New features
|
### 🎉 New features
|
||||||
|
|
||||||
- Add `--browser` flag to `eas login` for browser-based authentication. ([#3312](https://github.com/expo/eas-cli/pull/3312) by [@byronkarlen](https://github.com/byronkarlen))
|
|
||||||
- List available options in non-interactive mode errors instead of generic "unable to select" messages, making CLI usable by agents and scripts. ([#3359](https://github.com/expo/eas-cli/pull/3359) by [@EvanBacon](https://github.com/EvanBacon))
|
- List available options in non-interactive mode errors instead of generic "unable to select" messages, making CLI usable by agents and scripts. ([#3359](https://github.com/expo/eas-cli/pull/3359) by [@EvanBacon](https://github.com/EvanBacon))
|
||||||
- Hidden beta version of new `eas account:usage` command for viewing account usage and billing estimates. ([#3334](https://github.com/expo/eas-cli/pull/3334) by [@douglowder](https://github.com/douglowder), [@EvanBacon](https://github.com/EvanBacon))
|
- Hidden beta version of new `eas account:usage` command for viewing account usage and billing estimates. ([#3334](https://github.com/expo/eas-cli/pull/3334) by [@douglowder](https://github.com/douglowder), [@EvanBacon](https://github.com/EvanBacon))
|
||||||
- Add `--no-bytecode` and `--source-maps [mode]` flags to `eas update`. ([#3339](https://github.com/expo/eas-cli/pull/3339) by [@brentvatne](https://github.com/brentvatne))
|
- Add `--no-bytecode` and `--source-maps [mode]` flags to `eas update`. ([#3339](https://github.com/expo/eas-cli/pull/3339) by [@brentvatne](https://github.com/brentvatne))
|
||||||
|
|||||||
@@ -9,25 +9,21 @@ This is a **Lerna-based monorepo** containing the EAS CLI and all supporting bui
|
|||||||
### CLI & Configuration Packages
|
### CLI & Configuration Packages
|
||||||
|
|
||||||
1. **[packages/eas-cli](./packages/eas-cli/CLAUDE.md)** - The main CLI tool (published as `eas-cli`)
|
1. **[packages/eas-cli](./packages/eas-cli/CLAUDE.md)** - The main CLI tool (published as `eas-cli`)
|
||||||
|
|
||||||
- 95+ commands organized by domain (build, submit, update, channel, credentials, etc.)
|
- 95+ commands organized by domain (build, submit, update, channel, credentials, etc.)
|
||||||
- Built on oclif (Open CLI Framework)
|
- Built on oclif (Open CLI Framework)
|
||||||
- Entry point: `packages/eas-cli/bin/run`
|
- Entry point: `packages/eas-cli/bin/run`
|
||||||
|
|
||||||
2. **[packages/eas-json](./packages/eas-json/CLAUDE.md)** - EAS configuration parser (published as `@expo/eas-json`)
|
2. **[packages/eas-json](./packages/eas-json/CLAUDE.md)** - EAS configuration parser (published as `@expo/eas-json`)
|
||||||
|
|
||||||
- Validates `eas.json` configuration files using Joi schemas
|
- Validates `eas.json` configuration files using Joi schemas
|
||||||
- Manages build, submit, and deploy profiles
|
- Manages build, submit, and deploy profiles
|
||||||
- Supports JSON5 via golden-fleece
|
- Supports JSON5 via golden-fleece
|
||||||
|
|
||||||
3. **[packages/eas-build-cache-provider](./packages/eas-build-cache-provider/CLAUDE.md)** - Build cache plugin (published as `eas-build-cache-provider`)
|
3. **[packages/eas-build-cache-provider](./packages/eas-build-cache-provider/CLAUDE.md)** - Build cache plugin (published as `eas-build-cache-provider`)
|
||||||
|
|
||||||
- Optimizes build caching for Expo CLI
|
- Optimizes build caching for Expo CLI
|
||||||
|
|
||||||
### Build Execution Packages
|
### Build Execution Packages
|
||||||
|
|
||||||
4. **[packages/eas-build-job](./packages/eas-build-job)** - Build job definitions (published as `@expo/eas-build-job`)
|
4. **[packages/eas-build-job](./packages/eas-build-job)** - Build job definitions (published as `@expo/eas-build-job`)
|
||||||
|
|
||||||
- Defines all data structures for build operations (Job, BuildPhase, BuildMode, Platform, Workflow)
|
- Defines all data structures for build operations (Job, BuildPhase, BuildMode, Platform, Workflow)
|
||||||
- Provides type definitions and validation schemas (Zod, Joi)
|
- Provides type definitions and validation schemas (Zod, Joi)
|
||||||
- Contains BuildPhase enum that defines the traditional build pipeline stages
|
- Contains BuildPhase enum that defines the traditional build pipeline stages
|
||||||
@@ -35,7 +31,6 @@ This is a **Lerna-based monorepo** containing the EAS CLI and all supporting bui
|
|||||||
- **Foundation that all other build packages depend on**
|
- **Foundation that all other build packages depend on**
|
||||||
|
|
||||||
5. **[packages/build-tools](./packages/build-tools)** - Build execution engine (published as `@expo/build-tools`)
|
5. **[packages/build-tools](./packages/build-tools)** - Build execution engine (published as `@expo/build-tools`)
|
||||||
|
|
||||||
- Orchestrates all build operations through `BuildContext<T extends Job>`
|
- Orchestrates all build operations through `BuildContext<T extends Job>`
|
||||||
- Contains platform-specific builders: `androidBuilder()`, `iosBuilder()`, `runCustomBuildAsync()`
|
- Contains platform-specific builders: `androidBuilder()`, `iosBuilder()`, `runCustomBuildAsync()`
|
||||||
- Manages build phases, artifact uploading, caching, credentials
|
- Manages build phases, artifact uploading, caching, credentials
|
||||||
@@ -43,7 +38,6 @@ This is a **Lerna-based monorepo** containing the EAS CLI and all supporting bui
|
|||||||
- Integrates with GraphQL API
|
- Integrates with GraphQL API
|
||||||
|
|
||||||
6. **[packages/steps](./packages/steps)** - Custom build workflow engine (published as `@expo/steps`)
|
6. **[packages/steps](./packages/steps)** - Custom build workflow engine (published as `@expo/steps`)
|
||||||
|
|
||||||
- Framework for defining and executing custom build steps
|
- Framework for defining and executing custom build steps
|
||||||
- Key abstractions:
|
- Key abstractions:
|
||||||
- `BuildWorkflow`: Orchestrates sequential step execution
|
- `BuildWorkflow`: Orchestrates sequential step execution
|
||||||
@@ -55,7 +49,6 @@ This is a **Lerna-based monorepo** containing the EAS CLI and all supporting bui
|
|||||||
- Parses build configs from YAML/JSON
|
- Parses build configs from YAML/JSON
|
||||||
|
|
||||||
7. **[packages/local-build-plugin](./packages/local-build-plugin)** - Local build execution (published as `eas-cli-local-build-plugin`)
|
7. **[packages/local-build-plugin](./packages/local-build-plugin)** - Local build execution (published as `eas-cli-local-build-plugin`)
|
||||||
|
|
||||||
- Allows running EAS builds locally on developer machines
|
- Allows running EAS builds locally on developer machines
|
||||||
- Entry point: `packages/local-build-plugin/src/main.ts`
|
- Entry point: `packages/local-build-plugin/src/main.ts`
|
||||||
- Sets `EAS_BUILD_RUNNER=local-build-plugin` environment variable
|
- Sets `EAS_BUILD_RUNNER=local-build-plugin` environment variable
|
||||||
@@ -69,7 +62,6 @@ This is a **Lerna-based monorepo** containing the EAS CLI and all supporting bui
|
|||||||
### Supporting Packages
|
### Supporting Packages
|
||||||
|
|
||||||
9. **[packages/logger](./packages/logger)** - Bunyan-based structured logging (published as `@expo/logger`)
|
9. **[packages/logger](./packages/logger)** - Bunyan-based structured logging (published as `@expo/logger`)
|
||||||
|
|
||||||
- Used by all build packages
|
- Used by all build packages
|
||||||
|
|
||||||
10. **[packages/downloader](./packages/downloader)** - HTTP file downloading with retry logic (published as `@expo/downloader`)
|
10. **[packages/downloader](./packages/downloader)** - HTTP file downloading with retry logic (published as `@expo/downloader`)
|
||||||
@@ -106,8 +98,8 @@ yarn start-allow-unused
|
|||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn lerna run test # Run all tests across packages
|
yarn run -T lerna run test # Run all tests across packages
|
||||||
yarn lerna run test -- --watch # Watch mode across packages (heavier)
|
yarn run -T lerna run test -- --watch # Watch mode across packages (heavier)
|
||||||
#
|
#
|
||||||
# If you use `yarn test`, remember to pass args through two separators:
|
# If you use `yarn test`, remember to pass args through two separators:
|
||||||
# yarn test -- -- --watch
|
# yarn test -- -- --watch
|
||||||
@@ -124,7 +116,8 @@ cd packages/steps && yarn test
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn typecheck # TypeScript type check all packages
|
yarn typecheck # TypeScript type check all packages
|
||||||
yarn lint # ESLint across all packages
|
yarn lint # Oxlint across all packages
|
||||||
|
yarn fmt:check # Oxfmt format check
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running EAS CLI Locally
|
### Running EAS CLI Locally
|
||||||
@@ -307,7 +300,8 @@ All changes should be validated with TypeScript and the linter before committing
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn typecheck # Validate TypeScript types
|
yarn typecheck # Validate TypeScript types
|
||||||
yarn lint # Run ESLint
|
yarn lint # Run Oxlint
|
||||||
|
yarn fmt:check # Run Oxfmt format check
|
||||||
```
|
```
|
||||||
|
|
||||||
### Logging
|
### Logging
|
||||||
@@ -355,5 +349,5 @@ See `LICENSE` (MIT) and `LICENSE-BUSL` (BUSL-1.1) for details.
|
|||||||
## Important Notes
|
## Important Notes
|
||||||
|
|
||||||
- **Node Version**: Requires Node.js (managed via Mise)
|
- **Node Version**: Requires Node.js (managed via Mise)
|
||||||
- **Package Manager**: Uses Yarn 1.22.21
|
- **Package Manager**: Uses Yarn 4.12.0
|
||||||
- **Compilation Target**: CommonJS with Node resolution
|
- **Compilation Target**: CommonJS with Node resolution
|
||||||
+1
-1
@@ -69,7 +69,7 @@ See [`CLAUDE.md`](./CLAUDE.md) for how to set up your environment when making ch
|
|||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
From the repository root, run `yarn lerna run test`. For a single package, run `yarn test` in that package directory.
|
From the repository root, run `yarn run -T lerna run test`. For a single package, run `yarn test` in that package directory.
|
||||||
|
|
||||||
## Releasing
|
## Releasing
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"version": "17.0.0",
|
"version": "18.0.4",
|
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
[env]
|
||||||
|
_.path = "./tools/bin"
|
||||||
|
MISE_NODE_COREPACK = "true"
|
||||||
|
|
||||||
[tools]
|
[tools]
|
||||||
node = "20.19.4"
|
node = "20.19.4"
|
||||||
yarn = "1.22.22"
|
|
||||||
+9
-9
@@ -6,32 +6,32 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "lerna run build",
|
"build": "lerna run build",
|
||||||
"build:tarballs:linux": "yarn build-for-size-check",
|
|
||||||
"build-for-size-check": "yarn build && yarn workspace eas-cli pretarball-ci && CLI_SIZE_CHECK=1 yarn workspace eas-cli oclif pack:tarballs --no-xz --targets linux-x64",
|
|
||||||
"typecheck": "lerna run typecheck",
|
"typecheck": "lerna run typecheck",
|
||||||
"start": "lerna run watch --stream --parallel",
|
"start": "lerna run watch --stream --parallel",
|
||||||
"start-allow-unused": "lerna run watch-allow-unused --stream --parallel",
|
"start-allow-unused": "lerna run watch-allow-unused --stream --parallel",
|
||||||
"watch": "yarn start",
|
"watch": "yarn start",
|
||||||
"eas": "packages/eas-cli/bin/run",
|
"eas": "packages/eas-cli/bin/run",
|
||||||
"lint": "eslint .",
|
"lint": "oxlint --type-aware --import-plugin --tsconfig ./tsconfig.oxlint.json .",
|
||||||
|
"fmt": "oxfmt .",
|
||||||
|
"fmt:check": "oxfmt --check .",
|
||||||
"lint-changelog": "./scripts/bin/run lint-changelog",
|
"lint-changelog": "./scripts/bin/run lint-changelog",
|
||||||
"release": "lerna run rebuild && ./scripts/bin/run release",
|
"release": "lerna run rebuild --concurrency 1 && ./scripts/bin/run release",
|
||||||
"test": "lerna run test",
|
"test": "lerna run test",
|
||||||
"clean": "lerna run clean && rm -rf node_modules coverage"
|
"clean": "lerna run clean && rm -rf node_modules coverage"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "29.5.11",
|
"@types/jest": "29.5.11",
|
||||||
"@types/node": "20.14.8",
|
"@types/node": "20.14.8",
|
||||||
"eslint": "8.56.0",
|
|
||||||
"eslint-config-universe": "13.0.0",
|
|
||||||
"eslint-plugin-async-protect": "3.1.0",
|
"eslint-plugin-async-protect": "3.1.0",
|
||||||
"eslint-plugin-node": "11.1.0",
|
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-watch-typeahead": "2.2.2",
|
"jest-watch-typeahead": "2.2.2",
|
||||||
"lerna": "9.0.3",
|
"lerna": "9.0.3",
|
||||||
"oclif": "^3.9.0",
|
"oclif": "^3.9.0",
|
||||||
"prettier": "3.1.1",
|
"oxfmt": "0.28.0",
|
||||||
|
"oxlint": "1.43.0",
|
||||||
|
"oxlint-tsgolint": "0.11.1",
|
||||||
"ts-jest": "29.1.1",
|
"ts-jest": "29.1.1",
|
||||||
"typescript": "5.5.4"
|
"typescript": "5.5.4"
|
||||||
}
|
},
|
||||||
|
"packageManager": "[email protected]"
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../.eslintrc.js",
|
"extends": ["../../.oxlintrc.json"],
|
||||||
|
"jsPlugins": [
|
||||||
|
{
|
||||||
|
"name": "async-protect",
|
||||||
|
"specifier": "eslint-plugin-async-protect"
|
||||||
|
}
|
||||||
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"async-protect/async-suffix": "off",
|
"async-protect/async-suffix": "off",
|
||||||
"no-underscore-dangle": "off",
|
"no-underscore-dangle": "off",
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { Config } from '@jest/types';
|
import type { Config } from '@jest/types';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-relative-packages
|
|
||||||
import baseConfig from '../../../jest/jest.shared.config';
|
import baseConfig from '../../../jest/jest.shared.config';
|
||||||
|
|
||||||
const config: Config.InitialOptions = {
|
const config: Config.InitialOptions = {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { Config } from '@jest/types';
|
import type { Config } from '@jest/types';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-relative-packages
|
|
||||||
import baseConfig from '../../../jest/jest.shared.config';
|
import baseConfig from '../../../jest/jest.shared.config';
|
||||||
|
|
||||||
const config: Config.InitialOptions = {
|
const config: Config.InitialOptions = {
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "@expo/build-tools",
|
"name": "@expo/build-tools",
|
||||||
|
"version": "18.0.2",
|
||||||
|
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||||
|
"license": "BUSL-1.1",
|
||||||
|
"author": "Expo <[email protected]>",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/expo/eas-cli.git",
|
"url": "https://github.com/expo/eas-cli.git",
|
||||||
"directory": "packages/build-tools"
|
"directory": "packages/build-tools"
|
||||||
},
|
},
|
||||||
"version": "17.0.0",
|
|
||||||
"main": "dist/index.js",
|
|
||||||
"types": "dist/index.d.ts",
|
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
"resources",
|
"resources",
|
||||||
"templates",
|
"templates",
|
||||||
"bin"
|
"bin"
|
||||||
],
|
],
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "yarn watch",
|
"start": "yarn watch",
|
||||||
"prewatch": "yarn gql",
|
"prewatch": "yarn gql",
|
||||||
@@ -21,7 +24,7 @@
|
|||||||
"prebuild": "yarn gql",
|
"prebuild": "yarn gql",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"typecheck": "tsc",
|
"typecheck": "tsc",
|
||||||
"prepack": "rm -rf dist && tsc -p tsconfig.build.json",
|
"prepack": "rm -rf dist *.tsbuildinfo && yarn gql && tsc -p tsconfig.build.json",
|
||||||
"jest-unit": "jest --config jest/unit-config.ts",
|
"jest-unit": "jest --config jest/unit-config.ts",
|
||||||
"jest-integration": "jest --config jest/integration-config.ts",
|
"jest-integration": "jest --config jest/integration-config.ts",
|
||||||
"jest-unit-watch": "jest --config jest/unit-config.ts --watch",
|
"jest-unit-watch": "jest --config jest/unit-config.ts --watch",
|
||||||
@@ -31,26 +34,24 @@
|
|||||||
"gql:local": "API_SERVER_URL=http://api.expo.test yarn gql",
|
"gql:local": "API_SERVER_URL=http://api.expo.test yarn gql",
|
||||||
"test": "yarn jest-unit"
|
"test": "yarn jest-unit"
|
||||||
},
|
},
|
||||||
"author": "Expo <[email protected]>",
|
|
||||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
|
||||||
"license": "BUSL-1.1",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/config": "10.0.6",
|
"@expo/config": "10.0.6",
|
||||||
"@expo/config-plugins": "9.0.12",
|
"@expo/config-plugins": "9.0.12",
|
||||||
"@expo/downloader": "17.0.0",
|
"@expo/downloader": "18.0.1",
|
||||||
"@expo/eas-build-job": "17.0.0",
|
"@expo/eas-build-job": "18.0.2",
|
||||||
"@expo/env": "^0.4.0",
|
"@expo/env": "^0.4.0",
|
||||||
"@expo/logger": "17.0.0",
|
"@expo/logger": "18.0.1",
|
||||||
"@expo/package-manager": "1.9.10",
|
"@expo/package-manager": "1.9.10",
|
||||||
"@expo/plist": "^0.2.0",
|
"@expo/plist": "^0.2.0",
|
||||||
"@expo/results": "^1.0.0",
|
"@expo/results": "^1.0.0",
|
||||||
"@expo/spawn-async": "1.7.2",
|
"@expo/spawn-async": "1.7.2",
|
||||||
"@expo/steps": "17.0.0",
|
"@expo/steps": "18.0.2",
|
||||||
"@expo/template-file": "17.0.0",
|
"@expo/template-file": "18.0.1",
|
||||||
"@expo/turtle-spawn": "17.0.0",
|
"@expo/turtle-spawn": "18.0.1",
|
||||||
"@expo/xcpretty": "^4.3.1",
|
"@expo/xcpretty": "^4.3.1",
|
||||||
"@google-cloud/storage": "^7.11.2",
|
"@google-cloud/storage": "^7.11.2",
|
||||||
"@urql/core": "^6.0.1",
|
"@urql/core": "^6.0.1",
|
||||||
|
"bplist-parser": "0.3.2",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
"gql.tada": "^1.8.13",
|
"gql.tada": "^1.8.13",
|
||||||
@@ -59,6 +60,7 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"node-fetch": "^2.7.0",
|
"node-fetch": "^2.7.0",
|
||||||
"node-forge": "^1.3.1",
|
"node-forge": "^1.3.1",
|
||||||
|
"node-stream-zip": "1.15.0",
|
||||||
"nullthrows": "^1.1.1",
|
"nullthrows": "^1.1.1",
|
||||||
"plist": "^3.1.0",
|
"plist": "^3.1.0",
|
||||||
"promise-limit": "^2.7.0",
|
"promise-limit": "^2.7.0",
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ export async function resolveBuildPhaseErrorAsync(
|
|||||||
const userFacingError =
|
const userFacingError =
|
||||||
error instanceof errors.UserFacingError
|
error instanceof errors.UserFacingError
|
||||||
? error
|
? error
|
||||||
: resolveError(userErrorHandlers, logLines, errorContext, xcodeBuildLogs) ??
|
: (resolveError(userErrorHandlers, logLines, errorContext, xcodeBuildLogs) ??
|
||||||
new errors.UnknownError(errorContext.phase);
|
new errors.UnknownError(errorContext.phase));
|
||||||
const buildError = resolveError(buildErrorHandlers, logLines, errorContext, xcodeBuildLogs);
|
const buildError = resolveError(buildErrorHandlers, logLines, errorContext, xcodeBuildLogs);
|
||||||
|
|
||||||
const isUnknownUserError =
|
const isUnknownUserError =
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const userErrorHandlers: ErrorHandler<UserFacingError>[] = [
|
|||||||
`Your project requires a newer version of CocoaPods. You can update it in the build profile in eas.json by either:
|
`Your project requires a newer version of CocoaPods. You can update it in the build profile in eas.json by either:
|
||||||
- changing the current version under key "cocoapods"
|
- changing the current version under key "cocoapods"
|
||||||
- switching to an image that supports that version under key "image"`,
|
- switching to an image that supports that version under key "image"`,
|
||||||
'https://docs.expo.dev/build-reference/eas-json/'
|
{ docsUrl: 'https://docs.expo.dev/build-reference/eas-json/' }
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -30,7 +30,7 @@ export const userErrorHandlers: ErrorHandler<UserFacingError>[] = [
|
|||||||
new UserFacingError(
|
new UserFacingError(
|
||||||
'EAS_BUILD_UNSUPPORTED_BUNDLER_VERSION_ERROR',
|
'EAS_BUILD_UNSUPPORTED_BUNDLER_VERSION_ERROR',
|
||||||
`Your project requires a different version of the Ruby "bundler" program than the version installed in this EAS Build environment. You can specify which version of "bundler" to install by specifying the version under "build"→[buildProfileName]→"ios"→"bundler" in eas.json.`,
|
`Your project requires a different version of the Ruby "bundler" program than the version installed in this EAS Build environment. You can specify which version of "bundler" to install by specifying the version under "build"→[buildProfileName]→"ios"→"bundler" in eas.json.`,
|
||||||
'https://docs.expo.dev/build-reference/eas-json/'
|
{ docsUrl: 'https://docs.expo.dev/build-reference/eas-json/' }
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -70,7 +70,7 @@ export const userErrorHandlers: ErrorHandler<UserFacingError>[] = [
|
|||||||
new UserFacingError(
|
new UserFacingError(
|
||||||
'EAS_BUILD_MISSING_GOOGLE_SERVICES_JSON_ERROR',
|
'EAS_BUILD_MISSING_GOOGLE_SERVICES_JSON_ERROR',
|
||||||
'"google-services.json" is missing, make sure that the file exists. Remember that EAS Build only uploads the files tracked by git. Use EAS environment variables to provide EAS Build with the file.',
|
'"google-services.json" is missing, make sure that the file exists. Remember that EAS Build only uploads the files tracked by git. Use EAS environment variables to provide EAS Build with the file.',
|
||||||
'https://docs.expo.dev/eas/environment-variables/#file-environment-variables'
|
{ docsUrl: 'https://docs.expo.dev/eas/environment-variables/#file-environment-variables' }
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -85,7 +85,7 @@ export const userErrorHandlers: ErrorHandler<UserFacingError>[] = [
|
|||||||
new UserFacingError(
|
new UserFacingError(
|
||||||
'EAS_BUILD_MISSING_GOOGLE_SERVICES_JSON_ERROR',
|
'EAS_BUILD_MISSING_GOOGLE_SERVICES_JSON_ERROR',
|
||||||
'"google-services.json" is missing, make sure that the file exists. Remember that EAS Build only uploads the files tracked by git. Use EAS environment variables to provide EAS Build with the file.',
|
'"google-services.json" is missing, make sure that the file exists. Remember that EAS Build only uploads the files tracked by git. Use EAS environment variables to provide EAS Build with the file.',
|
||||||
'https://docs.expo.dev/eas/environment-variables/#file-environment-variables'
|
{ docsUrl: 'https://docs.expo.dev/eas/environment-variables/#file-environment-variables' }
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -98,7 +98,7 @@ export const userErrorHandlers: ErrorHandler<UserFacingError>[] = [
|
|||||||
new UserFacingError(
|
new UserFacingError(
|
||||||
'EAS_BUILD_MISSING_GOOGLE_SERVICES_PLIST_ERROR',
|
'EAS_BUILD_MISSING_GOOGLE_SERVICES_PLIST_ERROR',
|
||||||
'"GoogleService-Info.plist" is missing, make sure that the file exists. Remember that EAS Build only uploads the files tracked by git. Use EAS environment variables to provide EAS Build with the file.',
|
'"GoogleService-Info.plist" is missing, make sure that the file exists. Remember that EAS Build only uploads the files tracked by git. Use EAS environment variables to provide EAS Build with the file.',
|
||||||
'https://docs.expo.dev/eas/environment-variables/#file-environment-variables'
|
{ docsUrl: 'https://docs.expo.dev/eas/environment-variables/#file-environment-variables' }
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -234,13 +234,19 @@ You are seeing this error because either:
|
|||||||
'XCODE_RESOURCE_BUNDLE_CODE_SIGNING_ERROR',
|
'XCODE_RESOURCE_BUNDLE_CODE_SIGNING_ERROR',
|
||||||
`Starting from Xcode 14, resource bundles are signed by default, which requires setting the development team for each resource bundle target.
|
`Starting from Xcode 14, resource bundles are signed by default, which requires setting the development team for each resource bundle target.
|
||||||
To resolve this issue, downgrade to an older Xcode version using the "image" field in eas.json, or upgrade to SDK 46 or higher.`,
|
To resolve this issue, downgrade to an older Xcode version using the "image" field in eas.json, or upgrade to SDK 46 or higher.`,
|
||||||
'https://docs.expo.dev/build-reference/infrastructure/#ios-build-server-configurations'
|
{
|
||||||
|
docsUrl:
|
||||||
|
'https://docs.expo.dev/build-reference/infrastructure/#ios-build-server-configurations',
|
||||||
|
}
|
||||||
)
|
)
|
||||||
: new UserFacingError(
|
: new UserFacingError(
|
||||||
'XCODE_RESOURCE_BUNDLE_CODE_SIGNING_ERROR',
|
'XCODE_RESOURCE_BUNDLE_CODE_SIGNING_ERROR',
|
||||||
`Starting from Xcode 14, resource bundles are signed by default, which requires setting the development team for each resource bundle target.
|
`Starting from Xcode 14, resource bundles are signed by default, which requires setting the development team for each resource bundle target.
|
||||||
To resolve this issue, downgrade to an older Xcode version using the "image" field in eas.json, or turn off signing resource bundles in your Podfile: https://expo.fyi/r/disable-bundle-resource-signing`,
|
To resolve this issue, downgrade to an older Xcode version using the "image" field in eas.json, or turn off signing resource bundles in your Podfile: https://expo.fyi/r/disable-bundle-resource-signing`,
|
||||||
'https://docs.expo.dev/build-reference/infrastructure/#ios-build-server-configurations'
|
{
|
||||||
|
docsUrl:
|
||||||
|
'https://docs.expo.dev/build-reference/infrastructure/#ios-build-server-configurations',
|
||||||
|
}
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export async function eagerBundleAsync({
|
|||||||
export function shouldUseEagerBundle(metadata: Metadata | null | undefined): boolean {
|
export function shouldUseEagerBundle(metadata: Metadata | null | undefined): boolean {
|
||||||
return Boolean(
|
return Boolean(
|
||||||
!metadata?.developmentClient &&
|
!metadata?.developmentClient &&
|
||||||
metadata?.sdkVersion &&
|
metadata?.sdkVersion &&
|
||||||
semver.satisfies(metadata?.sdkVersion, '>=52')
|
semver.satisfies(metadata?.sdkVersion, '>=52')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,7 @@ import { createInstallNodeModulesBuildFunction } from './functions/installNodeMo
|
|||||||
import { createInstallPodsBuildFunction } from './functions/installPods';
|
import { createInstallPodsBuildFunction } from './functions/installPods';
|
||||||
import { createInternalEasMaestroTestFunction } from './functions/internalMaestroTest';
|
import { createInternalEasMaestroTestFunction } from './functions/internalMaestroTest';
|
||||||
import { createPrebuildBuildFunction } from './functions/prebuild';
|
import { createPrebuildBuildFunction } from './functions/prebuild';
|
||||||
|
import { createReadIpaInfoBuildFunction } from './functions/readIpaInfo';
|
||||||
import { createRepackBuildFunction } from './functions/repack';
|
import { createRepackBuildFunction } from './functions/repack';
|
||||||
import { resolveAppleTeamIdFromCredentialsFunction } from './functions/resolveAppleTeamIdFromCredentials';
|
import { resolveAppleTeamIdFromCredentialsFunction } from './functions/resolveAppleTeamIdFromCredentials';
|
||||||
import { createResolveBuildConfigBuildFunction } from './functions/resolveBuildConfig';
|
import { createResolveBuildConfigBuildFunction } from './functions/resolveBuildConfig';
|
||||||
@@ -48,6 +49,7 @@ export function getEasFunctions(ctx: CustomBuildContext): BuildFunction[] {
|
|||||||
createSetUpNpmrcBuildFunction(),
|
createSetUpNpmrcBuildFunction(),
|
||||||
createInstallNodeModulesBuildFunction(),
|
createInstallNodeModulesBuildFunction(),
|
||||||
createPrebuildBuildFunction(),
|
createPrebuildBuildFunction(),
|
||||||
|
createReadIpaInfoBuildFunction(),
|
||||||
createDownloadBuildFunction(),
|
createDownloadBuildFunction(),
|
||||||
createRepackBuildFunction(),
|
createRepackBuildFunction(),
|
||||||
createRestoreCacheFunction(),
|
createRestoreCacheFunction(),
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,71 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
jest.unmock('fs');
|
||||||
|
jest.unmock('node:fs');
|
||||||
|
jest.unmock('fs/promises');
|
||||||
|
jest.unmock('node:fs/promises');
|
||||||
|
|
||||||
|
import { createGlobalContextMock } from '../../../__tests__/utils/context';
|
||||||
|
import { createReadIpaInfoBuildFunction, readIpaInfoAsync } from '../readIpaInfo';
|
||||||
|
|
||||||
|
const NO_INFO_PLIST_IPA_BASE64 =
|
||||||
|
'UEsDBAoAAAAAAPRcUFwAAAAAAAAAAAAAAAAIABwAUGF5bG9hZC9VVAkAA+zzkmns85JpdXgLAAEE9QEAAAQUAAAAUEsDBAoAAAAAAPRcUFwAAAAAAAAAAAAAAAAUABwAUGF5bG9hZC9UZXN0QXBwLmFwcC9VVAkAA+zzkmns85JpdXgLAAEE9QEAAAQUAAAAUEsDBAoAAAAAAPRcUFwgMDo2BgAAAAYAAAAeABwAUGF5bG9hZC9UZXN0QXBwLmFwcC9SZWFkbWUudHh0VVQJAAPs85Jp7POSaXV4CwABBPUBAAAEFAAAAGhlbGxvClBLAQIeAwoAAAAAAPRcUFwAAAAAAAAAAAAAAAAIABgAAAAAAAAAEADtQQAAAABQYXlsb2FkL1VUBQAD7POSaXV4CwABBPUBAAAEFAAAAFBLAQIeAwoAAAAAAPRcUFwAAAAAAAAAAAAAAAAUABgAAAAAAAAAEADtQUIAAABQYXlsb2FkL1Rlc3RBcHAuYXBwL1VUBQAD7POSaXV4CwABBPUBAAAEFAAAAFBLAQIeAwoAAAAAAPRcUFwgMDo2BgAAAAYAAAAeABgAAAAAAAEAAACkgZAAAABQYXlsb2FkL1Rlc3RBcHAuYXBwL1JlYWRtZS50eHRVVAUAA+zzkml1eAsAAQT1AQAABBQAAABQSwUGAAAAAAMAAwAMAQAA7gAAAAAA';
|
||||||
|
const MISSING_BUNDLE_VERSION_IPA_BASE64 =
|
||||||
|
'UEsDBAoAAAAAAPRcUFwAAAAAAAAAAAAAAAAIABwAUGF5bG9hZC9VVAkAA+zzkmns85JpdXgLAAEE9QEAAAQUAAAAUEsDBAoAAAAAAPRcUFwAAAAAAAAAAAAAAAAUABwAUGF5bG9hZC9UZXN0QXBwLmFwcC9VVAkAA+zzkmns85JpdXgLAAEE9QEAAAQUAAAAUEsDBBQAAAAIAPRcUFz9BfqM2wAAAEQBAAAeABwAUGF5bG9hZC9UZXN0QXBwLmFwcC9JbmZvLnBsaXN0VVQJAAPs85Jp7POSaXV4CwABBPUBAAAEFAAAAGWOUUvDMBSF3/srYt6b6/RFJMtw7QaFooV2go+jybZgm4Tkard/b7IKor7ec+73Hb46jwP5VD5oa5Z0wW4pUaa3Upvjku66bf5AVyLjN+VL0b01G+IGHZA0u3VdFYTmAE/ODQqg7ErS1FXbkcgA2DxTQk+I7hFgmia2Ty3W2zEVAzTeOuXxUkdYHh+YREmjZqb/mhOvUvcoMkL4u7qIYrv+MHJQlVQG9UErzyHdUx7Qx90iapg678ekRBUwyjl8Z38x7cl6fJ2F7bXyD7dgd+z+B8Bh3sPhulZkX1BLAQIeAwoAAAAAAPRcUFwAAAAAAAAAAAAAAAAIABgAAAAAAAAAEADtQQAAAABQYXlsb2FkL1VUBQAD7POSaXV4CwABBPUBAAAEFAAAAFBLAQIeAwoAAAAAAPRcUFwAAAAAAAAAAAAAAAAUABgAAAAAAAAAEADtQUIAAABQYXlsb2FkL1Rlc3RBcHAuYXBwL1VUBQAD7POSaXV4CwABBPUBAAAEFAAAAFBLAQIeAxQAAAAIAPRcUFz9BfqM2wAAAEQBAAAeABgAAAAAAAEAAACkgZAAAABQYXlsb2FkL1Rlc3RBcHAuYXBwL0luZm8ucGxpc3RVVAUAA+zzkml1eAsAAQT1AQAABBQAAABQSwUGAAAAAAMAAwAMAQAAwwEAAAAA';
|
||||||
|
const IPA_FIXTURE_SOURCE_PATH = path.join(__dirname, 'fixtures/SmallestAppExample.ipa');
|
||||||
|
|
||||||
|
async function writeIpaFileAsync(filePath: string, base64Data: string): Promise<void> {
|
||||||
|
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
||||||
|
await fs.promises.writeFile(filePath, base64Data, { encoding: 'base64' });
|
||||||
|
}
|
||||||
|
|
||||||
|
describe(readIpaInfoAsync, () => {
|
||||||
|
it('reads bundle identifier and versions from fixture ipa', async () => {
|
||||||
|
await expect(readIpaInfoAsync(IPA_FIXTURE_SOURCE_PATH)).resolves.toEqual({
|
||||||
|
bundleIdentifier: 'dev.expo.SmallestAppExample',
|
||||||
|
bundleShortVersion: '1.0',
|
||||||
|
bundleVersion: '1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when Info.plist cannot be found in IPA', async () => {
|
||||||
|
const ipaPath = '/tmp/no-info.ipa';
|
||||||
|
await writeIpaFileAsync(ipaPath, NO_INFO_PLIST_IPA_BASE64);
|
||||||
|
|
||||||
|
await expect(readIpaInfoAsync(ipaPath)).rejects.toThrow(
|
||||||
|
`Failed to read IPA info: Could not find Info.plist in ${ipaPath}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when required plist fields are missing', async () => {
|
||||||
|
const ipaPath = '/tmp/missing-bundle-version.ipa';
|
||||||
|
await writeIpaFileAsync(ipaPath, MISSING_BUNDLE_VERSION_IPA_BASE64);
|
||||||
|
|
||||||
|
await expect(readIpaInfoAsync(ipaPath)).rejects.toThrow(
|
||||||
|
/Failed to read IPA info: .*CFBundleVersion/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(createReadIpaInfoBuildFunction, () => {
|
||||||
|
it('sets build step outputs', async () => {
|
||||||
|
const readIpaInfo = createReadIpaInfoBuildFunction();
|
||||||
|
const globalContext = createGlobalContextMock({
|
||||||
|
staticContextContent: {
|
||||||
|
job: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const buildStep = readIpaInfo.createBuildStepFromFunctionCall(globalContext, {
|
||||||
|
callInputs: {
|
||||||
|
ipa_path: IPA_FIXTURE_SOURCE_PATH,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await buildStep.executeAsync();
|
||||||
|
|
||||||
|
expect(buildStep.outputById.bundle_identifier.value).toBe('dev.expo.SmallestAppExample');
|
||||||
|
expect(buildStep.outputById.bundle_short_version.value).toBe('1.0');
|
||||||
|
expect(buildStep.outputById.bundle_version.value).toBe('1');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -71,7 +71,7 @@ async function uploadApplicationArchivesAsync({
|
|||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const applicationArchivePatternOrPath =
|
const applicationArchivePatternOrPath =
|
||||||
ctx.job.platform === Platform.ANDROID
|
ctx.job.platform === Platform.ANDROID
|
||||||
? ctx.job.applicationArchivePath ?? 'android/app/build/outputs/**/*.{apk,aab}'
|
? (ctx.job.applicationArchivePath ?? 'android/app/build/outputs/**/*.{apk,aab}')
|
||||||
: resolveIosArtifactPath(ctx.job);
|
: resolveIosArtifactPath(ctx.job);
|
||||||
const applicationArchives = await findArtifacts({
|
const applicationArchives = await findArtifacts({
|
||||||
rootDir: workingDirectory,
|
rootDir: workingDirectory,
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import { UserFacingError } from '@expo/eas-build-job/dist/errors';
|
||||||
|
import {
|
||||||
|
BuildFunction,
|
||||||
|
BuildStepInput,
|
||||||
|
BuildStepInputValueTypeName,
|
||||||
|
BuildStepOutput,
|
||||||
|
} from '@expo/steps';
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
import path from 'node:path';
|
||||||
|
import StreamZip from 'node-stream-zip';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import bplistParser from 'bplist-parser';
|
||||||
|
import plist from 'plist';
|
||||||
|
|
||||||
|
export type IpaInfo = {
|
||||||
|
bundleIdentifier: string;
|
||||||
|
bundleShortVersion: string;
|
||||||
|
bundleVersion: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const INFO_PLIST_PATH_REGEXP = /^Payload\/[^/]+\.app\/Info\.plist$/;
|
||||||
|
|
||||||
|
export function createReadIpaInfoBuildFunction(): BuildFunction {
|
||||||
|
return new BuildFunction({
|
||||||
|
namespace: 'eas',
|
||||||
|
id: 'read_ipa_info',
|
||||||
|
name: 'Read IPA Info',
|
||||||
|
__metricsId: 'eas/read_ipa_info',
|
||||||
|
inputProviders: [
|
||||||
|
BuildStepInput.createProvider({
|
||||||
|
id: 'ipa_path',
|
||||||
|
required: true,
|
||||||
|
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
outputProviders: [
|
||||||
|
BuildStepOutput.createProvider({
|
||||||
|
id: 'bundle_identifier',
|
||||||
|
required: true,
|
||||||
|
}),
|
||||||
|
BuildStepOutput.createProvider({
|
||||||
|
id: 'bundle_short_version',
|
||||||
|
required: true,
|
||||||
|
}),
|
||||||
|
BuildStepOutput.createProvider({
|
||||||
|
id: 'bundle_version',
|
||||||
|
required: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
fn: async (stepCtx, { inputs, outputs }) => {
|
||||||
|
const ipaPathInput = z.string().parse(inputs.ipa_path.value);
|
||||||
|
const ipaPath = path.resolve(stepCtx.workingDirectory, ipaPathInput);
|
||||||
|
if (!(await fs.pathExists(ipaPath))) {
|
||||||
|
throw new UserFacingError(
|
||||||
|
'EAS_READ_IPA_INFO_FILE_NOT_FOUND',
|
||||||
|
`IPA file not found: ${ipaPath}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ipaInfo = await readIpaInfoAsync(ipaPath);
|
||||||
|
outputs.bundle_identifier.set(ipaInfo.bundleIdentifier);
|
||||||
|
outputs.bundle_short_version.set(ipaInfo.bundleShortVersion);
|
||||||
|
outputs.bundle_version.set(ipaInfo.bundleVersion);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function readIpaInfoAsync(ipaPath: string): Promise<IpaInfo> {
|
||||||
|
try {
|
||||||
|
const infoPlistBuffer = await readInfoPlistBufferFromIpaAsync(ipaPath);
|
||||||
|
const infoPlist = parseInfoPlistBuffer(infoPlistBuffer);
|
||||||
|
|
||||||
|
const bundleIdentifier = infoPlist.CFBundleIdentifier;
|
||||||
|
if (typeof bundleIdentifier !== 'string') {
|
||||||
|
throw new UserFacingError(
|
||||||
|
'EAS_READ_IPA_INFO_INVALID_INFO_PLIST',
|
||||||
|
'Failed to read IPA info: Missing or invalid CFBundleIdentifier in Info.plist'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundleShortVersion = infoPlist.CFBundleShortVersionString;
|
||||||
|
if (typeof bundleShortVersion !== 'string') {
|
||||||
|
throw new UserFacingError(
|
||||||
|
'EAS_READ_IPA_INFO_INVALID_INFO_PLIST',
|
||||||
|
'Failed to read IPA info: Missing or invalid CFBundleShortVersionString in Info.plist'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundleVersion = infoPlist.CFBundleVersion;
|
||||||
|
if (typeof bundleVersion !== 'string') {
|
||||||
|
throw new UserFacingError(
|
||||||
|
'EAS_READ_IPA_INFO_INVALID_INFO_PLIST',
|
||||||
|
'Failed to read IPA info: Missing or invalid CFBundleVersion in Info.plist'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
bundleIdentifier,
|
||||||
|
bundleShortVersion,
|
||||||
|
bundleVersion,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof UserFacingError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UserFacingError(
|
||||||
|
'EAS_READ_IPA_INFO_FAILED',
|
||||||
|
`Failed to read IPA info: ${(error as Error).message}`,
|
||||||
|
{ cause: error }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseInfoPlistBuffer(data: Buffer): Record<string, unknown> {
|
||||||
|
const isBinaryPlist = data.subarray(0, 8).toString('ascii') === 'bplist00';
|
||||||
|
if (isBinaryPlist) {
|
||||||
|
const parsedBinaryPlists = bplistParser.parseBuffer(data);
|
||||||
|
const parsedBinaryPlist = parsedBinaryPlists[0];
|
||||||
|
if (!parsedBinaryPlist || typeof parsedBinaryPlist !== 'object') {
|
||||||
|
throw new UserFacingError(
|
||||||
|
'EAS_READ_IPA_INFO_INVALID_BINARY_PLIST',
|
||||||
|
'Invalid binary plist in IPA'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return parsedBinaryPlist as Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return plist.parse(data.toString('utf8')) as Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readInfoPlistBufferFromIpaAsync(ipaPath: string): Promise<Buffer> {
|
||||||
|
const zip = new StreamZip.async({ file: ipaPath });
|
||||||
|
try {
|
||||||
|
const entries = Object.values(await zip.entries());
|
||||||
|
const infoPlistEntry = entries.find(entry => INFO_PLIST_PATH_REGEXP.test(entry.name));
|
||||||
|
if (!infoPlistEntry) {
|
||||||
|
throw new UserFacingError(
|
||||||
|
'EAS_READ_IPA_INFO_INFO_PLIST_NOT_FOUND',
|
||||||
|
`Failed to read IPA info: Could not find Info.plist in ${ipaPath}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return await zip.entryData(infoPlistEntry.name);
|
||||||
|
} finally {
|
||||||
|
await zip.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -234,7 +234,7 @@ export function createUploadToAscBuildFunction(): BuildFunction {
|
|||||||
|
|
||||||
stepsCtx.logger.info('Checking build upload status...');
|
stepsCtx.logger.info('Checking build upload status...');
|
||||||
const waitingForBuildStartedAt = Date.now();
|
const waitingForBuildStartedAt = Date.now();
|
||||||
while (Date.now() - waitingForBuildStartedAt < 10 * 60 * 1000 /* 10 minutes */) {
|
while (Date.now() - waitingForBuildStartedAt < 30 * 60 * 1000 /* 30 minutes */) {
|
||||||
const {
|
const {
|
||||||
data: {
|
data: {
|
||||||
attributes: { state },
|
attributes: { state },
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../.eslintrc.js",
|
|
||||||
"rules": {
|
|
||||||
"async-protect/async-suffix": "off",
|
|
||||||
"import/no-extraneous-dependencies": ["off"],
|
|
||||||
"no-console": "off",
|
|
||||||
"no-underscore-dangle": "off",
|
|
||||||
"no-void": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"extends": ["../../.oxlintrc.json"],
|
||||||
|
"jsPlugins": [
|
||||||
|
{
|
||||||
|
"name": "async-protect",
|
||||||
|
"specifier": "eslint-plugin-async-protect"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"async-protect/async-suffix": "off",
|
||||||
|
"no-console": "off",
|
||||||
|
"no-underscore-dangle": "off",
|
||||||
|
"no-void": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { Config } from '@jest/types';
|
import type { Config } from '@jest/types';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-relative-packages
|
|
||||||
import baseConfig from '../../jest/jest.shared.config';
|
import baseConfig from '../../jest/jest.shared.config';
|
||||||
|
|
||||||
const config: Config.InitialOptions = {
|
const config: Config.InitialOptions = {
|
||||||
|
|||||||
@@ -1,40 +1,41 @@
|
|||||||
{
|
{
|
||||||
"name": "create-eas-build-function",
|
"name": "create-eas-build-function",
|
||||||
|
"version": "18.0.3",
|
||||||
|
"description": "Create functions for use in EAS Build custom builds.",
|
||||||
|
"keywords": [
|
||||||
|
"expo",
|
||||||
|
"react",
|
||||||
|
"react-native"
|
||||||
|
],
|
||||||
|
"homepage": "https://docs.expo.dev",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"author": "Expo <[email protected]>",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/expo/eas-cli.git",
|
"url": "https://github.com/expo/eas-cli.git",
|
||||||
"directory": "packages/create-eas-build-function"
|
"directory": "packages/create-eas-build-function"
|
||||||
},
|
},
|
||||||
"version": "17.0.0",
|
"bin": "./build/index.js",
|
||||||
"bin": {
|
|
||||||
"create-eas-build-function": "./build/index.js"
|
|
||||||
},
|
|
||||||
"main": "build",
|
|
||||||
"description": "Create functions for use in EAS Build custom builds.",
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"keywords": [
|
|
||||||
"expo",
|
|
||||||
"react-native",
|
|
||||||
"react"
|
|
||||||
],
|
|
||||||
"homepage": "https://docs.expo.dev",
|
|
||||||
"author": "Expo <[email protected]>",
|
|
||||||
"files": [
|
"files": [
|
||||||
"build",
|
"build",
|
||||||
"templates"
|
"templates"
|
||||||
],
|
],
|
||||||
|
"main": "build",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepack": "yarn run clean && yarn run build",
|
"prepack": "yarn run clean && yarn run build",
|
||||||
"lint": "eslint .",
|
"lint": "oxlint --type-aware --tsconfig ../../tsconfig.oxlint.json .",
|
||||||
"test": "echo 'No tests yet.'",
|
"test": "echo 'No tests yet.'",
|
||||||
"watch": "yarn run build:dev -w",
|
"watch": "yarn run build:dev -w",
|
||||||
"build:dev": "ncc build ./src/index.ts -o build/",
|
"build:dev": "ncc build ./src/index.ts -o build/",
|
||||||
"build": "ncc build ./src/index.ts -o build/ --minify --no-cache --no-source-map-register",
|
"build": "ncc build ./src/index.ts -o build/ --minify --no-cache --no-source-map-register",
|
||||||
"typecheck": "tsc",
|
"typecheck": "tsc",
|
||||||
"clean": "rimraf ./build/ *.tsbuildinfo"
|
"clean": "rimraf ./build/ \"*.tsbuildinfo\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/steps": "17.0.0"
|
"@expo/steps": "18.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@expo/package-manager": "1.9.10",
|
"@expo/package-manager": "1.9.10",
|
||||||
@@ -46,12 +47,11 @@
|
|||||||
"chalk": "4.1.2",
|
"chalk": "4.1.2",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
"ora": "^6.3.1",
|
"ora": "^6.3.1",
|
||||||
|
"oxlint": "1.43.0",
|
||||||
"prompts": "^2.4.2",
|
"prompts": "^2.4.2",
|
||||||
|
"rimraf": "3.0.2",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.5.4",
|
"typescript": "^5.5.4",
|
||||||
"update-check": "^1.5.4"
|
"update-check": "^1.5.4"
|
||||||
},
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "custom-js-function",
|
"name": "custom-js-function",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "./build/index.js",
|
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"devDependencies": {
|
"main": "./build/index.js",
|
||||||
"@vercel/ncc": "^0.38.1",
|
|
||||||
"@babel/cli": "^7.24.8",
|
|
||||||
"@babel/core": "^7.24.9",
|
|
||||||
"@babel/preset-env": "^7.24.8"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"babel": "babel src --out-dir babel",
|
"babel": "babel src --out-dir babel",
|
||||||
"build": "yarn babel && ncc build ./babel/index.js -o build/ --minify --no-cache --no-source-map-register"
|
"build": "yarn babel && ncc build ./babel/index.js -o build/ --minify --no-cache --no-source-map-register"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/cli": "^7.24.8",
|
||||||
|
"@babel/core": "^7.24.9",
|
||||||
|
"@babel/preset-env": "^7.24.8",
|
||||||
|
"@vercel/ncc": "^0.38.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,17 +2,17 @@
|
|||||||
"name": "custom-ts-function",
|
"name": "custom-ts-function",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "./build/index.js",
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
|
"main": "./build/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "ncc build ./src/index.ts -o build/ --minify --no-cache --no-source-map-register"
|
"build": "ncc build ./src/index.ts -o build/ --minify --no-cache --no-source-map-register"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vercel/ncc": "^0.38.1",
|
"@expo/steps": "^1.0.119",
|
||||||
"@types/node": "20.14.11",
|
"@types/node": "20.14.11",
|
||||||
"typescript": "^5.5.3",
|
"@vercel/ncc": "^0.38.1",
|
||||||
"@expo/steps": "^1.0.119"
|
"typescript": "^5.5.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../.eslintrc.js",
|
|
||||||
"rules": {
|
|
||||||
"async-protect/async-suffix": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"extends": ["../../.oxlintrc.json"],
|
||||||
|
"jsPlugins": [
|
||||||
|
{
|
||||||
|
"name": "async-protect",
|
||||||
|
"specifier": "eslint-plugin-async-protect"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"async-protect/async-suffix": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { Config } from '@jest/types';
|
import type { Config } from '@jest/types';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-relative-packages
|
|
||||||
import baseConfig from '../../jest/jest.shared.config';
|
import baseConfig from '../../jest/jest.shared.config';
|
||||||
|
|
||||||
const config: Config.InitialOptions = {
|
const config: Config.InitialOptions = {
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
{
|
{
|
||||||
"name": "@expo/downloader",
|
"name": "@expo/downloader",
|
||||||
|
"version": "18.0.1",
|
||||||
|
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||||
|
"license": "BUSL-1.1",
|
||||||
|
"author": "Expo <[email protected]>",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/expo/eas-cli.git",
|
"url": "https://github.com/expo/eas-cli.git",
|
||||||
"directory": "packages/downloader"
|
"directory": "packages/downloader"
|
||||||
},
|
},
|
||||||
"version": "17.0.0",
|
|
||||||
"main": "dist/index.js",
|
|
||||||
"types": "dist/index.d.ts",
|
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "yarn watch",
|
"start": "yarn watch",
|
||||||
"watch": "tsc --watch --preserveWatchOutput",
|
"watch": "tsc --watch --preserveWatchOutput",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"typecheck": "tsc",
|
"typecheck": "tsc",
|
||||||
"prepack": "rm -rf dist && tsc -p tsconfig.build.json",
|
"prepack": "rm -rf dist *.tsbuildinfo && tsc -p tsconfig.build.json",
|
||||||
"test": "jest --config jest.config.ts",
|
"test": "jest --config jest.config.ts",
|
||||||
"test:watch": "jest --config jest.config.ts --watch",
|
"test:watch": "jest --config jest.config.ts --watch",
|
||||||
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
|
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
|
||||||
},
|
},
|
||||||
"author": "Expo <[email protected]>",
|
|
||||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
|
||||||
"license": "BUSL-1.1",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
"got": "11.8.5"
|
"got": "11.8.5"
|
||||||
|
|||||||
@@ -12,7 +12,5 @@
|
|||||||
"rootDir": "./src",
|
"rootDir": "./src",
|
||||||
"outDir": "./dist"
|
"outDir": "./dist"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*"]
|
||||||
"src/**/*"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": ["../../.oxlintrc.json"],
|
||||||
|
"jsPlugins": [
|
||||||
|
{
|
||||||
|
"name": "async-protect",
|
||||||
|
"specifier": "eslint-plugin-async-protect"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -48,6 +48,7 @@ Update your **app.json** to enable the EAS build cache provider:
|
|||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
The build cache provider:
|
The build cache provider:
|
||||||
|
|
||||||
1. Hooks into Expo CLI's build process
|
1. Hooks into Expo CLI's build process
|
||||||
2. Uploads build cache artifacts to EAS infrastructure
|
2. Uploads build cache artifacts to EAS infrastructure
|
||||||
3. Downloads cached artifacts on subsequent builds
|
3. Downloads cached artifacts on subsequent builds
|
||||||
|
|||||||
@@ -1,9 +1,35 @@
|
|||||||
{
|
{
|
||||||
"name": "eas-build-cache-provider",
|
"name": "eas-build-cache-provider",
|
||||||
|
"version": "18.0.2",
|
||||||
"description": "A build cache provider plugin for the Expo CLI",
|
"description": "A build cache provider plugin for the Expo CLI",
|
||||||
"version": "17.0.0",
|
"homepage": "https://github.com/expo/eas-cli",
|
||||||
"author": "Expo <[email protected]>",
|
|
||||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Expo <[email protected]>",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/expo/eas-cli.git",
|
||||||
|
"directory": "packages/eas-build-cache-provider"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"/build"
|
||||||
|
],
|
||||||
|
"main": "build/index.js",
|
||||||
|
"types": "build/index.d.ts",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc --project tsconfig.build.json",
|
||||||
|
"build-allow-unused": "tsc --project tsconfig.allowUnused.json",
|
||||||
|
"watch": "yarn build --watch --preserveWatchOutput",
|
||||||
|
"watch-allow-unused": "yarn build-allow-unused --watch --preserveWatchOutput",
|
||||||
|
"typecheck": "tsc",
|
||||||
|
"prepack": "yarn rebuild",
|
||||||
|
"rebuild": "rimraf build *.tsbuildinfo && yarn build",
|
||||||
|
"test": "jest --passWithNoTests",
|
||||||
|
"clean": "rimraf build node_modules yarn-error.log *.tsbuildinfo"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "7.23.5",
|
"@babel/code-frame": "7.23.5",
|
||||||
"@expo/config": "11.0.10",
|
"@expo/config": "11.0.10",
|
||||||
@@ -22,37 +48,12 @@
|
|||||||
"@types/babel__code-frame": "7.0.3",
|
"@types/babel__code-frame": "7.0.3",
|
||||||
"@types/fs-extra": "11.0.4",
|
"@types/fs-extra": "11.0.4",
|
||||||
"@types/getenv": "^1.0.0",
|
"@types/getenv": "^1.0.0",
|
||||||
|
"jest": "29.7.0",
|
||||||
"memfs": "3.4.13",
|
"memfs": "3.4.13",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"typescript": "5.5.4"
|
"typescript": "5.5.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
},
|
|
||||||
"homepage": "https://github.com/expo/eas-cli",
|
|
||||||
"license": "MIT",
|
|
||||||
"main": "build/index.js",
|
|
||||||
"types": "build/index.d.ts",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/expo/eas-cli.git",
|
|
||||||
"directory": "packages/eas-build-cache-provider"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc --project tsconfig.build.json",
|
|
||||||
"build-allow-unused": "tsc --project tsconfig.allowUnused.json",
|
|
||||||
"watch": "yarn build --watch --preserveWatchOutput",
|
|
||||||
"watch-allow-unused": "yarn build-allow-unused --watch --preserveWatchOutput",
|
|
||||||
"typecheck": "tsc",
|
|
||||||
"prepack": "yarn rebuild",
|
|
||||||
"rebuild": "rimraf build && yarn build",
|
|
||||||
"test": "jest --passWithNoTests",
|
|
||||||
"clean": "rimraf build node_modules yarn-error.log *.tsbuildinfo"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"/build"
|
|
||||||
],
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
extends: ['universe/shared/typescript-analysis'],
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ['*.ts', '*.d.ts'],
|
|
||||||
parserOptions: {
|
|
||||||
project: './packages/eas-build-cache-provider/tsconfig.json',
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/explicit-function-return-type': [
|
|
||||||
'warn',
|
|
||||||
{
|
|
||||||
allowExpressions: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": ["../.oxlintrc.json"],
|
||||||
|
"jsPlugins": [
|
||||||
|
{
|
||||||
|
"name": "async-protect",
|
||||||
|
"specifier": "eslint-plugin-async-protect"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"typescript/explicit-function-return-type": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"allowExpressions": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../.eslintrc.js"
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": ["../../.oxlintrc.json"],
|
||||||
|
"jsPlugins": [
|
||||||
|
{
|
||||||
|
"name": "async-protect",
|
||||||
|
"specifier": "eslint-plugin-async-protect"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { Config } from '@jest/types';
|
import type { Config } from '@jest/types';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-relative-packages
|
|
||||||
import baseConfig from '../../jest/jest.shared.config';
|
import baseConfig from '../../jest/jest.shared.config';
|
||||||
|
|
||||||
const config: Config.InitialOptions = {
|
const config: Config.InitialOptions = {
|
||||||
|
|||||||
@@ -1,40 +1,40 @@
|
|||||||
{
|
{
|
||||||
"name": "@expo/eas-build-job",
|
"name": "@expo/eas-build-job",
|
||||||
|
"version": "18.0.2",
|
||||||
|
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Expo <[email protected]>",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/expo/eas-cli.git",
|
"url": "https://github.com/expo/eas-cli.git",
|
||||||
"directory": "packages/eas-build-job"
|
"directory": "packages/eas-build-job"
|
||||||
},
|
},
|
||||||
"version": "17.0.0",
|
|
||||||
"main": "dist/index.js",
|
|
||||||
"types": "dist/index.d.ts",
|
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "yarn watch",
|
"start": "yarn watch",
|
||||||
"watch": "tsc --watch --preserveWatchOutput",
|
"watch": "tsc --watch --preserveWatchOutput",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"typecheck": "tsc",
|
"typecheck": "tsc",
|
||||||
"prepack": "rm -rf dist && tsc -p tsconfig.build.json",
|
"prepack": "rm -rf dist *.tsbuildinfo && tsc -p tsconfig.build.json",
|
||||||
"test": "jest --config jest.config.ts",
|
"test": "jest --config jest.config.ts",
|
||||||
"test:watch": "jest --config jest.config.ts --watch",
|
"test:watch": "jest --config jest.config.ts --watch",
|
||||||
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
|
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
|
||||||
},
|
},
|
||||||
"author": "Expo <[email protected]>",
|
"dependencies": {
|
||||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
"@expo/logger": "18.0.1",
|
||||||
"license": "MIT",
|
"joi": "^17.13.1",
|
||||||
|
"semver": "^7.6.2",
|
||||||
|
"zod": "^4.3.5"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/node": "20.14.2",
|
"@types/node": "20.14.2",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"ts-jest": "^29.1.4",
|
"ts-jest": "^29.1.4",
|
||||||
"typescript": "^5.5.4"
|
"typescript": "^5.5.4"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@expo/logger": "17.0.0",
|
|
||||||
"joi": "^17.13.1",
|
|
||||||
"semver": "^7.6.2",
|
|
||||||
"zod": "^4.3.5"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -340,6 +340,60 @@ describe('Android.JobSchema', () => {
|
|||||||
expect(error).toBeFalsy();
|
expect(error).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('can set hooks with arbitrary names', () => {
|
||||||
|
const job = {
|
||||||
|
mode: BuildMode.BUILD,
|
||||||
|
type: Workflow.GENERIC,
|
||||||
|
platform: Platform.ANDROID,
|
||||||
|
projectArchive: {
|
||||||
|
type: ArchiveSourceType.URL,
|
||||||
|
url: 'https://expo.dev/builds/123',
|
||||||
|
},
|
||||||
|
projectRootDirectory: '.',
|
||||||
|
hooks: {
|
||||||
|
before_install_node_modules: [
|
||||||
|
{
|
||||||
|
id: 'before-install',
|
||||||
|
run: 'echo before install',
|
||||||
|
shell: 'sh',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
my_custom_hook: [],
|
||||||
|
},
|
||||||
|
secrets,
|
||||||
|
initiatingUserId: randomUUID(),
|
||||||
|
appId: randomUUID(),
|
||||||
|
};
|
||||||
|
const { value, error } = Android.JobSchema.validate(job, joiOptions);
|
||||||
|
expect(value).toMatchObject(job);
|
||||||
|
expect(error).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can set hooks in custom mode', () => {
|
||||||
|
const customBuildJob = {
|
||||||
|
mode: BuildMode.CUSTOM,
|
||||||
|
type: Workflow.UNKNOWN,
|
||||||
|
platform: Platform.ANDROID,
|
||||||
|
projectArchive: {
|
||||||
|
type: ArchiveSourceType.URL,
|
||||||
|
url: 'https://expo.dev/builds/123',
|
||||||
|
},
|
||||||
|
projectRootDirectory: '.',
|
||||||
|
customBuildConfig: {
|
||||||
|
path: 'production.android.yml',
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
before_install_node_modules: [],
|
||||||
|
},
|
||||||
|
initiatingUserId: randomUUID(),
|
||||||
|
appId: randomUUID(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { value, error } = Android.JobSchema.validate(customBuildJob, joiOptions);
|
||||||
|
expect(value).toMatchObject(customBuildJob);
|
||||||
|
expect(error).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
test('can set github trigger options', () => {
|
test('can set github trigger options', () => {
|
||||||
const job = {
|
const job = {
|
||||||
mode: BuildMode.BUILD,
|
mode: BuildMode.BUILD,
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { UserFacingError } from '../errors';
|
||||||
|
|
||||||
|
describe(UserFacingError, () => {
|
||||||
|
it('supports docsUrl in options', () => {
|
||||||
|
const error = new UserFacingError('ERR_CODE', 'message', {
|
||||||
|
docsUrl: 'https://docs.example.dev',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(error.docsUrl).toBe('https://docs.example.dev');
|
||||||
|
expect(error.cause).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports cause in options', () => {
|
||||||
|
const cause = new Error('root cause');
|
||||||
|
const error = new UserFacingError('ERR_CODE', 'message', { cause });
|
||||||
|
|
||||||
|
expect(error.cause).toBe(cause);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports docsUrl and cause in options', () => {
|
||||||
|
const cause = new Error('root cause');
|
||||||
|
const error = new UserFacingError('ERR_CODE', 'message', {
|
||||||
|
docsUrl: 'https://docs.example.dev',
|
||||||
|
cause,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(error.docsUrl).toBe('https://docs.example.dev');
|
||||||
|
expect(error.cause).toBe(cause);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -92,6 +92,107 @@ describe('Generic.JobZ', () => {
|
|||||||
expect(Generic.JobZ.parse(job)).toEqual(job);
|
expect(Generic.JobZ.parse(job)).toEqual(job);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('accepts hooks with arbitrary names', () => {
|
||||||
|
const job: Generic.Job = {
|
||||||
|
projectArchive: {
|
||||||
|
type: ArchiveSourceType.GIT,
|
||||||
|
repositoryUrl: 'https://github.com/expo/expo.git',
|
||||||
|
gitCommitHash: '1234567890',
|
||||||
|
gitRef: null,
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
before_install_node_modules: [
|
||||||
|
{
|
||||||
|
id: 'before-install',
|
||||||
|
run: 'echo before install',
|
||||||
|
shell: 'sh',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
my_custom_hook: [],
|
||||||
|
},
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
id: 'step1',
|
||||||
|
name: 'Step 1',
|
||||||
|
run: 'echo Hello, world!',
|
||||||
|
shell: 'sh',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
secrets: {
|
||||||
|
robotAccessToken: 'token',
|
||||||
|
environmentSecrets: [
|
||||||
|
{
|
||||||
|
name: 'secret-name',
|
||||||
|
value: 'secret-value',
|
||||||
|
type: EnvironmentSecretType.STRING,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
expoDevUrl: 'https://expo.dev/accounts/name/builds/id',
|
||||||
|
builderEnvironment: {
|
||||||
|
image: 'macos-sonoma-14.5-xcode-15.4',
|
||||||
|
node: '20.15.1',
|
||||||
|
corepack: false,
|
||||||
|
env: {
|
||||||
|
KEY1: 'value1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
triggeredBy: BuildTrigger.GIT_BASED_INTEGRATION,
|
||||||
|
appId: randomUUID(),
|
||||||
|
initiatingUserId: randomUUID(),
|
||||||
|
};
|
||||||
|
expect(Generic.JobZ.parse(job)).toEqual(job);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors when hook step definition is invalid', () => {
|
||||||
|
const job = {
|
||||||
|
projectArchive: {
|
||||||
|
type: ArchiveSourceType.GIT,
|
||||||
|
repositoryUrl: 'https://github.com/expo/expo.git',
|
||||||
|
gitCommitHash: '1234567890',
|
||||||
|
gitRef: null,
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
before_install_node_modules: [
|
||||||
|
{
|
||||||
|
id: 'missing-run-or-uses',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
id: 'step1',
|
||||||
|
name: 'Step 1',
|
||||||
|
run: 'echo Hello, world!',
|
||||||
|
shell: 'sh',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
secrets: {
|
||||||
|
robotAccessToken: 'token',
|
||||||
|
environmentSecrets: [
|
||||||
|
{
|
||||||
|
name: 'secret-name',
|
||||||
|
value: 'secret-value',
|
||||||
|
type: EnvironmentSecretType.STRING,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
expoDevUrl: 'https://expo.dev/accounts/name/builds/id',
|
||||||
|
builderEnvironment: {
|
||||||
|
image: 'macos-sonoma-14.5-xcode-15.4',
|
||||||
|
node: '20.15.1',
|
||||||
|
corepack: false,
|
||||||
|
env: {
|
||||||
|
KEY1: 'value1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
triggeredBy: BuildTrigger.GIT_BASED_INTEGRATION,
|
||||||
|
appId: randomUUID(),
|
||||||
|
initiatingUserId: randomUUID(),
|
||||||
|
};
|
||||||
|
expect(() => Generic.JobZ.parse(job)).toThrow(ZodError);
|
||||||
|
});
|
||||||
|
|
||||||
it('errors when steps are not provided', () => {
|
it('errors when steps are not provided', () => {
|
||||||
const job: Omit<Generic.Job, 'customBuildConfig' | 'steps'> = {
|
const job: Omit<Generic.Job, 'customBuildConfig' | 'steps'> = {
|
||||||
projectArchive: {
|
projectArchive: {
|
||||||
|
|||||||
@@ -84,6 +84,66 @@ describe('Ios.JobSchema', () => {
|
|||||||
expect(error).toBeFalsy();
|
expect(error).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('valid generic job with hooks', () => {
|
||||||
|
const genericJob = {
|
||||||
|
secrets: {
|
||||||
|
buildCredentials,
|
||||||
|
},
|
||||||
|
type: Workflow.GENERIC,
|
||||||
|
platform: Platform.IOS,
|
||||||
|
projectArchive: {
|
||||||
|
type: ArchiveSourceType.URL,
|
||||||
|
url: 'http://localhost:3000',
|
||||||
|
},
|
||||||
|
projectRootDirectory: '.',
|
||||||
|
hooks: {
|
||||||
|
before_install_node_modules: [
|
||||||
|
{
|
||||||
|
id: 'before-install',
|
||||||
|
run: 'echo before install',
|
||||||
|
shell: 'sh',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
my_custom_hook: [],
|
||||||
|
},
|
||||||
|
initiatingUserId: randomUUID(),
|
||||||
|
appId: randomUUID(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { value, error } = Ios.JobSchema.validate(genericJob, joiOptions);
|
||||||
|
expect(value).toMatchObject(genericJob);
|
||||||
|
expect(error).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('valid resign job with hooks', () => {
|
||||||
|
const genericJob = {
|
||||||
|
mode: BuildMode.RESIGN,
|
||||||
|
secrets: {
|
||||||
|
buildCredentials,
|
||||||
|
},
|
||||||
|
type: Workflow.UNKNOWN,
|
||||||
|
platform: Platform.IOS,
|
||||||
|
projectArchive: {
|
||||||
|
type: ArchiveSourceType.NONE,
|
||||||
|
},
|
||||||
|
resign: {
|
||||||
|
applicationArchiveSource: {
|
||||||
|
type: ArchiveSourceType.URL,
|
||||||
|
url: 'http://localhost:3000/a.ipa',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
before_install_node_modules: [],
|
||||||
|
},
|
||||||
|
initiatingUserId: randomUUID(),
|
||||||
|
appId: randomUUID(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { value, error } = Ios.JobSchema.validate(genericJob, joiOptions);
|
||||||
|
expect(value).toMatchObject(genericJob);
|
||||||
|
expect(error).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
test('valid custom build job with metadataLocation', () => {
|
test('valid custom build job with metadataLocation', () => {
|
||||||
const customBuildJob = {
|
const customBuildJob = {
|
||||||
mode: BuildMode.CUSTOM,
|
mode: BuildMode.CUSTOM,
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import {
|
|||||||
EnvSchema,
|
EnvSchema,
|
||||||
EnvironmentSecret,
|
EnvironmentSecret,
|
||||||
EnvironmentSecretsSchema,
|
EnvironmentSecretsSchema,
|
||||||
|
Hooks,
|
||||||
|
HooksSchema,
|
||||||
Platform,
|
Platform,
|
||||||
StaticWorkflowInterpolationContext,
|
StaticWorkflowInterpolationContext,
|
||||||
StaticWorkflowInterpolationContextZ,
|
StaticWorkflowInterpolationContextZ,
|
||||||
@@ -106,6 +108,7 @@ export interface Job {
|
|||||||
customBuildConfig?: {
|
customBuildConfig?: {
|
||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
|
hooks?: Hooks;
|
||||||
steps?: Step[];
|
steps?: Step[];
|
||||||
outputs?: Record<string, string>;
|
outputs?: Record<string, string>;
|
||||||
|
|
||||||
@@ -172,6 +175,7 @@ export const JobSchema = Joi.object({
|
|||||||
|
|
||||||
buildType: Joi.string().valid(...Object.values(BuildType)),
|
buildType: Joi.string().valid(...Object.values(BuildType)),
|
||||||
username: Joi.string(),
|
username: Joi.string(),
|
||||||
|
hooks: HooksSchema,
|
||||||
|
|
||||||
experimental: Joi.object({
|
experimental: Joi.object({
|
||||||
prebuildCommand: Joi.string(),
|
prebuildCommand: Joi.string(),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Joi from 'joi';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { BuildPhase, BuildPhaseResult } from './logs';
|
import { BuildPhase, BuildPhaseResult } from './logs';
|
||||||
import { validateSteps } from './step';
|
import { Step, StepZ, validateSteps } from './step';
|
||||||
|
|
||||||
export enum BuildMode {
|
export enum BuildMode {
|
||||||
BUILD = 'build',
|
BUILD = 'build',
|
||||||
@@ -142,6 +142,21 @@ export const EnvironmentSecretZ = z.object({
|
|||||||
type: z.nativeEnum(EnvironmentSecretType),
|
type: z.nativeEnum(EnvironmentSecretType),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type Hooks = Record<string, Step[]>;
|
||||||
|
export const HooksSchema = Joi.object().pattern(
|
||||||
|
Joi.string(),
|
||||||
|
Joi.array()
|
||||||
|
.items(Joi.any())
|
||||||
|
.required()
|
||||||
|
.custom(steps => {
|
||||||
|
if (steps.length === 0) {
|
||||||
|
return steps;
|
||||||
|
}
|
||||||
|
return validateSteps(steps);
|
||||||
|
}, 'steps validation')
|
||||||
|
);
|
||||||
|
export const HooksZ = z.record(z.string(), z.array(StepZ));
|
||||||
|
|
||||||
export interface Cache {
|
export interface Cache {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
clear: boolean;
|
clear: boolean;
|
||||||
|
|||||||
@@ -54,12 +54,15 @@ export class BuildError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class UserFacingError extends Error {
|
export class UserFacingError extends Error {
|
||||||
|
public docsUrl?: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public errorCode: string,
|
public errorCode: string,
|
||||||
public message: string,
|
public message: string,
|
||||||
public docsUrl?: string
|
options?: { docsUrl?: string; cause?: unknown }
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message, { cause: options?.cause });
|
||||||
|
this.docsUrl = options?.docsUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
ArchiveSourceSchemaZ,
|
ArchiveSourceSchemaZ,
|
||||||
BuildTrigger,
|
BuildTrigger,
|
||||||
EnvironmentSecretZ,
|
EnvironmentSecretZ,
|
||||||
|
HooksZ,
|
||||||
StaticWorkflowInterpolationContextZ,
|
StaticWorkflowInterpolationContextZ,
|
||||||
} from './common';
|
} from './common';
|
||||||
import { StepZ } from './step';
|
import { StepZ } from './step';
|
||||||
@@ -45,6 +46,7 @@ export namespace Generic {
|
|||||||
initiatingUserId: z.string(),
|
initiatingUserId: z.string(),
|
||||||
appId: z.string(),
|
appId: z.string(),
|
||||||
|
|
||||||
|
hooks: HooksZ.optional(),
|
||||||
steps: z.array(StepZ).min(1),
|
steps: z.array(StepZ).min(1),
|
||||||
outputs: z.record(z.string(), z.string()).optional(),
|
outputs: z.record(z.string(), z.string()).optional(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export {
|
|||||||
Env,
|
Env,
|
||||||
EnvironmentSecret,
|
EnvironmentSecret,
|
||||||
EnvironmentSecretType,
|
EnvironmentSecretType,
|
||||||
|
Hooks,
|
||||||
Workflow,
|
Workflow,
|
||||||
Platform,
|
Platform,
|
||||||
Cache,
|
Cache,
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import {
|
|||||||
EnvSchema,
|
EnvSchema,
|
||||||
EnvironmentSecret,
|
EnvironmentSecret,
|
||||||
EnvironmentSecretsSchema,
|
EnvironmentSecretsSchema,
|
||||||
|
Hooks,
|
||||||
|
HooksSchema,
|
||||||
Platform,
|
Platform,
|
||||||
StaticWorkflowInterpolationContext,
|
StaticWorkflowInterpolationContext,
|
||||||
StaticWorkflowInterpolationContextZ,
|
StaticWorkflowInterpolationContextZ,
|
||||||
@@ -120,6 +122,7 @@ export interface Job {
|
|||||||
customBuildConfig?: {
|
customBuildConfig?: {
|
||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
|
hooks?: Hooks;
|
||||||
steps?: Step[];
|
steps?: Step[];
|
||||||
outputs?: Record<string, string>;
|
outputs?: Record<string, string>;
|
||||||
|
|
||||||
@@ -206,6 +209,7 @@ export const JobSchema = Joi.object({
|
|||||||
applicationArchivePath: Joi.string(),
|
applicationArchivePath: Joi.string(),
|
||||||
|
|
||||||
username: Joi.string(),
|
username: Joi.string(),
|
||||||
|
hooks: HooksSchema,
|
||||||
|
|
||||||
experimental: Joi.object({
|
experimental: Joi.object({
|
||||||
prebuildCommand: Joi.string(),
|
prebuildCommand: Joi.string(),
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
const rootEslintrc = require('../../.eslintrc.js');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
rules: {
|
|
||||||
'no-restricted-properties': [
|
|
||||||
'warn',
|
|
||||||
{
|
|
||||||
object: 'it',
|
|
||||||
property: 'only',
|
|
||||||
message: 'it.only should not be committed to main.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
object: 'test',
|
|
||||||
property: 'only',
|
|
||||||
message: 'test.only should not be committed to main.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
object: 'describe',
|
|
||||||
property: 'only',
|
|
||||||
message: 'describe.only should not be committed to main.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'no-restricted-imports': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
paths: [
|
|
||||||
...rootEslintrc.rules['no-restricted-imports'][1].paths,
|
|
||||||
{
|
|
||||||
name: 'ora',
|
|
||||||
message: 'Import ora from src/ora.ts instead.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'graphql/template-strings': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
env: 'apollo',
|
|
||||||
schemaJson: require('./graphql.schema.json'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'graphql/named-operations': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
schemaJson: require('./graphql.schema.json'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'graphql/required-fields': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
env: 'apollo',
|
|
||||||
schemaJson: require('./graphql.schema.json'),
|
|
||||||
requiredFields: ['id'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'graphql/capitalized-type-name': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
schemaJson: require('./graphql.schema.json'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
plugins: ['graphql'],
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
{
|
||||||
|
"extends": ["../../.oxlintrc.json"],
|
||||||
|
"jsPlugins": [
|
||||||
|
{
|
||||||
|
"name": "async-protect",
|
||||||
|
"specifier": "eslint-plugin-async-protect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "graphql",
|
||||||
|
"specifier": "eslint-plugin-graphql"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"no-restricted-properties": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"object": "it",
|
||||||
|
"property": "only",
|
||||||
|
"message": "it.only should not be committed to main."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "test",
|
||||||
|
"property": "only",
|
||||||
|
"message": "test.only should not be committed to main."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "describe",
|
||||||
|
"property": "only",
|
||||||
|
"message": "describe.only should not be committed to main."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-restricted-imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"name": "lodash",
|
||||||
|
"message": "Don't use lodash, it's heavy!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ora",
|
||||||
|
"message": "Import ora from src/ora.ts instead."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"graphql/template-strings": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"env": "apollo",
|
||||||
|
"schemaJsonFilepath": "packages/eas-cli/graphql.schema.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"graphql/named-operations": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"schemaJsonFilepath": "packages/eas-cli/graphql.schema.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"graphql/required-fields": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"env": "apollo",
|
||||||
|
"schemaJsonFilepath": "packages/eas-cli/graphql.schema.json",
|
||||||
|
"requiredFields": ["id"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"graphql/capitalized-type-name": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"schemaJsonFilepath": "packages/eas-cli/graphql.schema.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ The main CLI tool for Expo Application Services (EAS). Published as `eas-cli` on
|
|||||||
## Development Commands
|
## Development Commands
|
||||||
|
|
||||||
### Running Locally
|
### Running Locally
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# From repository root:
|
# From repository root:
|
||||||
yarn eas <command>
|
yarn eas <command>
|
||||||
@@ -26,6 +27,7 @@ easd build --help
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Build & Development
|
### Build & Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn build # TypeScript compilation (src → build)
|
yarn build # TypeScript compilation (src → build)
|
||||||
yarn watch # Watch mode
|
yarn watch # Watch mode
|
||||||
@@ -35,22 +37,26 @@ yarn typecheck-for-build # Type check for production build
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn test # Run all tests
|
yarn test # Run all tests
|
||||||
yarn test <path-to-file> # Run specific test file
|
yarn test <path-to-file> # Run specific test file
|
||||||
```
|
```
|
||||||
|
|
||||||
### GraphQL Code Generation
|
### GraphQL Code Generation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn generate-graphql-code # Generate TypeScript types from GraphQL schema
|
yarn generate-graphql-code # Generate TypeScript types from GraphQL schema
|
||||||
yarn verify-graphql-code # Verify GraphQL code is up to date (CI check)
|
yarn verify-graphql-code # Verify GraphQL code is up to date (CI check)
|
||||||
```
|
```
|
||||||
|
|
||||||
The GraphQL schema is fetched from `https://staging-api.expo.dev/graphql` and generates:
|
The GraphQL schema is fetched from `https://staging-api.expo.dev/graphql` and generates:
|
||||||
|
|
||||||
- `src/graphql/generated.ts` - 499KB+ of TypeScript types
|
- `src/graphql/generated.ts` - 499KB+ of TypeScript types
|
||||||
- Types for all queries, mutations, and fragments
|
- Types for all queries, mutations, and fragments
|
||||||
|
|
||||||
### oclif Commands
|
### oclif Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn oclif readme # Auto-generate README.md from command metadata
|
yarn oclif readme # Auto-generate README.md from command metadata
|
||||||
```
|
```
|
||||||
@@ -81,6 +87,7 @@ Available context types:
|
|||||||
- Provides: methods to interact with server-side env vars
|
- Provides: methods to interact with server-side env vars
|
||||||
|
|
||||||
Example command structure:
|
Example command structure:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand from '../../commandUtils/EasCommand';
|
||||||
|
|
||||||
@@ -88,7 +95,7 @@ export default class Build extends EasCommand {
|
|||||||
static override description = 'start a build';
|
static override description = 'start a build';
|
||||||
|
|
||||||
static override ContextOptions = {
|
static override ContextOptions = {
|
||||||
...EasCommand.ContextOptions.LoggedIn, // Requires auth
|
...EasCommand.ContextOptions.LoggedIn, // Requires auth
|
||||||
...EasCommand.ContextOptions.ProjectConfig, // Requires project
|
...EasCommand.ContextOptions.ProjectConfig, // Requires project
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -120,12 +127,14 @@ export default class Build extends EasCommand {
|
|||||||
**Location**: `src/graphql/`
|
**Location**: `src/graphql/`
|
||||||
|
|
||||||
**Client**: `ExpoGraphqlClient` (urql-based wrapper)
|
**Client**: `ExpoGraphqlClient` (urql-based wrapper)
|
||||||
|
|
||||||
- Automatic retry logic with exponential backoff
|
- Automatic retry logic with exponential backoff
|
||||||
- Cache management (cache-first vs network-only)
|
- Cache management (cache-first vs network-only)
|
||||||
- Transient error handling
|
- Transient error handling
|
||||||
- Authentication via Bearer token or session secret
|
- Authentication via Bearer token or session secret
|
||||||
|
|
||||||
**Structure**:
|
**Structure**:
|
||||||
|
|
||||||
```
|
```
|
||||||
src/graphql/
|
src/graphql/
|
||||||
├── generated.ts # Generated TypeScript types
|
├── generated.ts # Generated TypeScript types
|
||||||
@@ -135,15 +144,14 @@ src/graphql/
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Making GraphQL Requests**:
|
**Making GraphQL Requests**:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// In a command with LoggedIn context:
|
// In a command with LoggedIn context:
|
||||||
const { graphqlClient } = await this.getContextAsync(LoggedIn, {
|
const { graphqlClient } = await this.getContextAsync(LoggedIn, {
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await graphqlClient
|
const result = await graphqlClient.query(MyQuery, { variables: { id: projectId } }).toPromise();
|
||||||
.query(MyQuery, { variables: { id: projectId } })
|
|
||||||
.toPromise();
|
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
throw new Error(result.error.message);
|
throw new Error(result.error.message);
|
||||||
@@ -157,6 +165,7 @@ const data = result.data;
|
|||||||
**Client**: `ApiV2Client` in `src/api.ts`
|
**Client**: `ApiV2Client` in `src/api.ts`
|
||||||
|
|
||||||
**Base URLs**:
|
**Base URLs**:
|
||||||
|
|
||||||
- Production: `https://api.expo.dev`
|
- Production: `https://api.expo.dev`
|
||||||
- Staging: `https://staging-api.expo.dev` (set `EXPO_STAGING=1`)
|
- Staging: `https://staging-api.expo.dev` (set `EXPO_STAGING=1`)
|
||||||
- Local: `http://127.0.0.1:3000` (set `EXPO_LOCAL=1`)
|
- Local: `http://127.0.0.1:3000` (set `EXPO_LOCAL=1`)
|
||||||
@@ -202,6 +211,7 @@ src/
|
|||||||
## Common Patterns
|
## Common Patterns
|
||||||
|
|
||||||
### Working with eas.json
|
### Working with eas.json
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
|
import { EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
|
||||||
|
|
||||||
@@ -214,6 +224,7 @@ const buildProfile = easJson.build?.[profileName];
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Project Context
|
### Project Context
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// In a command with ProjectConfig context:
|
// In a command with ProjectConfig context:
|
||||||
const { projectId, exp } = await this.getContextAsync(ProjectConfig, {
|
const { projectId, exp } = await this.getContextAsync(ProjectConfig, {
|
||||||
@@ -243,6 +254,7 @@ const { projectId, exp } = await this.getContextAsync(ProjectConfig, {
|
|||||||
### Error Handling
|
### Error Handling
|
||||||
|
|
||||||
Extend `CommandError` or use specific error classes in `src/commandUtils/errors.ts`:
|
Extend `CommandError` or use specific error classes in `src/commandUtils/errors.ts`:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { CommandError } from '../commandUtils/errors';
|
import { CommandError } from '../commandUtils/errors';
|
||||||
|
|
||||||
@@ -264,6 +276,7 @@ throw new CommandError('BUILD_FAILED', 'The build failed due to...');
|
|||||||
- Utilities: `ts-mockito`, `mockdate`
|
- Utilities: `ts-mockito`, `mockdate`
|
||||||
|
|
||||||
Example test:
|
Example test:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { vol } from 'memfs';
|
import { vol } from 'memfs';
|
||||||
import nock from 'nock';
|
import nock from 'nock';
|
||||||
|
|||||||
+94
-92
@@ -165,10 +165,11 @@ log in with your Expo account
|
|||||||
|
|
||||||
```
|
```
|
||||||
USAGE
|
USAGE
|
||||||
$ eas account:login [-s]
|
$ eas account:login [-s] [-b]
|
||||||
|
|
||||||
FLAGS
|
FLAGS
|
||||||
-s, --sso Login with SSO
|
-b, --browser Login with your browser
|
||||||
|
-s, --sso Login with SSO
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
log in with your Expo account
|
log in with your Expo account
|
||||||
@@ -177,7 +178,7 @@ ALIASES
|
|||||||
$ eas login
|
$ eas login
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/account/login.ts)_
|
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/account/login.ts)_
|
||||||
|
|
||||||
## `eas account:logout`
|
## `eas account:logout`
|
||||||
|
|
||||||
@@ -194,7 +195,7 @@ ALIASES
|
|||||||
$ eas logout
|
$ eas logout
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/account/logout.ts)_
|
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/account/logout.ts)_
|
||||||
|
|
||||||
## `eas account:usage [ACCOUNT_NAME]`
|
## `eas account:usage [ACCOUNT_NAME]`
|
||||||
|
|
||||||
@@ -216,7 +217,7 @@ DESCRIPTION
|
|||||||
view account usage and billing for the current cycle
|
view account usage and billing for the current cycle
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/account/usage.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/account/usage.ts)_
|
_See code: [packages/eas-cli/src/commands/account/usage.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/account/usage.ts)_
|
||||||
|
|
||||||
## `eas account:view`
|
## `eas account:view`
|
||||||
|
|
||||||
@@ -233,7 +234,7 @@ ALIASES
|
|||||||
$ eas whoami
|
$ eas whoami
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/account/view.ts)_
|
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/account/view.ts)_
|
||||||
|
|
||||||
## `eas analytics [STATUS]`
|
## `eas analytics [STATUS]`
|
||||||
|
|
||||||
@@ -247,7 +248,7 @@ DESCRIPTION
|
|||||||
display or change analytics settings
|
display or change analytics settings
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/analytics.ts)_
|
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/analytics.ts)_
|
||||||
|
|
||||||
## `eas autocomplete [SHELL]`
|
## `eas autocomplete [SHELL]`
|
||||||
|
|
||||||
@@ -299,7 +300,7 @@ DESCRIPTION
|
|||||||
create a branch
|
create a branch
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/branch/create.ts)_
|
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/branch/create.ts)_
|
||||||
|
|
||||||
## `eas branch:delete [NAME]`
|
## `eas branch:delete [NAME]`
|
||||||
|
|
||||||
@@ -320,7 +321,7 @@ DESCRIPTION
|
|||||||
delete a branch
|
delete a branch
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/branch/delete.ts)_
|
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||||
|
|
||||||
## `eas branch:list`
|
## `eas branch:list`
|
||||||
|
|
||||||
@@ -340,7 +341,7 @@ DESCRIPTION
|
|||||||
list all branches
|
list all branches
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/branch/list.ts)_
|
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/branch/list.ts)_
|
||||||
|
|
||||||
## `eas branch:rename`
|
## `eas branch:rename`
|
||||||
|
|
||||||
@@ -360,7 +361,7 @@ DESCRIPTION
|
|||||||
rename a branch
|
rename a branch
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/branch/rename.ts)_
|
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||||
|
|
||||||
## `eas branch:view [NAME]`
|
## `eas branch:view [NAME]`
|
||||||
|
|
||||||
@@ -383,7 +384,7 @@ DESCRIPTION
|
|||||||
view a branch
|
view a branch
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/branch/view.ts)_
|
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/branch/view.ts)_
|
||||||
|
|
||||||
## `eas build`
|
## `eas build`
|
||||||
|
|
||||||
@@ -424,7 +425,7 @@ DESCRIPTION
|
|||||||
start a build
|
start a build
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/build/index.ts)_
|
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/index.ts)_
|
||||||
|
|
||||||
## `eas build:cancel [BUILD_ID]`
|
## `eas build:cancel [BUILD_ID]`
|
||||||
|
|
||||||
@@ -443,7 +444,7 @@ DESCRIPTION
|
|||||||
cancel a build
|
cancel a build
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/build/cancel.ts)_
|
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||||
|
|
||||||
## `eas build:configure`
|
## `eas build:configure`
|
||||||
|
|
||||||
@@ -460,7 +461,7 @@ DESCRIPTION
|
|||||||
configure the project to support EAS Build
|
configure the project to support EAS Build
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/build/configure.ts)_
|
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/configure.ts)_
|
||||||
|
|
||||||
## `eas build:delete [BUILD_ID]`
|
## `eas build:delete [BUILD_ID]`
|
||||||
|
|
||||||
@@ -479,7 +480,7 @@ DESCRIPTION
|
|||||||
delete a build
|
delete a build
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/build/delete.ts)_
|
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/delete.ts)_
|
||||||
|
|
||||||
## `eas build:dev`
|
## `eas build:dev`
|
||||||
|
|
||||||
@@ -499,7 +500,7 @@ DESCRIPTION
|
|||||||
run dev client simulator/emulator build with matching fingerprint or create a new one
|
run dev client simulator/emulator build with matching fingerprint or create a new one
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/dev.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/build/dev.ts)_
|
_See code: [packages/eas-cli/src/commands/build/dev.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/dev.ts)_
|
||||||
|
|
||||||
## `eas build:download`
|
## `eas build:download`
|
||||||
|
|
||||||
@@ -520,7 +521,7 @@ DESCRIPTION
|
|||||||
download simulator/emulator builds for a given fingerprint hash
|
download simulator/emulator builds for a given fingerprint hash
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/download.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/build/download.ts)_
|
_See code: [packages/eas-cli/src/commands/build/download.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/download.ts)_
|
||||||
|
|
||||||
## `eas build:inspect`
|
## `eas build:inspect`
|
||||||
|
|
||||||
@@ -555,7 +556,7 @@ DESCRIPTION
|
|||||||
inspect the state of the project at specific build stages, useful for troubleshooting
|
inspect the state of the project at specific build stages, useful for troubleshooting
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/build/inspect.ts)_
|
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||||
|
|
||||||
## `eas build:list`
|
## `eas build:list`
|
||||||
|
|
||||||
@@ -607,7 +608,7 @@ DESCRIPTION
|
|||||||
list all builds for your project
|
list all builds for your project
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/build/list.ts)_
|
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/list.ts)_
|
||||||
|
|
||||||
## `eas build:resign`
|
## `eas build:resign`
|
||||||
|
|
||||||
@@ -636,7 +637,7 @@ DESCRIPTION
|
|||||||
re-sign a build archive
|
re-sign a build archive
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/build/resign.ts)_
|
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/resign.ts)_
|
||||||
|
|
||||||
## `eas build:run`
|
## `eas build:run`
|
||||||
|
|
||||||
@@ -662,7 +663,7 @@ DESCRIPTION
|
|||||||
run simulator/emulator builds from eas-cli
|
run simulator/emulator builds from eas-cli
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/build/run.ts)_
|
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/run.ts)_
|
||||||
|
|
||||||
## `eas build:submit`
|
## `eas build:submit`
|
||||||
|
|
||||||
@@ -716,7 +717,7 @@ DESCRIPTION
|
|||||||
get the latest version from EAS servers
|
get the latest version from EAS servers
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/build/version/get.ts)_
|
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/version/get.ts)_
|
||||||
|
|
||||||
## `eas build:version:set`
|
## `eas build:version:set`
|
||||||
|
|
||||||
@@ -735,7 +736,7 @@ DESCRIPTION
|
|||||||
update version of an app
|
update version of an app
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/build/version/set.ts)_
|
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||||
|
|
||||||
## `eas build:version:sync`
|
## `eas build:version:sync`
|
||||||
|
|
||||||
@@ -754,7 +755,7 @@ DESCRIPTION
|
|||||||
update a version in native code with a value stored on EAS servers
|
update a version in native code with a value stored on EAS servers
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/build/version/sync.ts)_
|
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||||
|
|
||||||
## `eas build:view [BUILD_ID]`
|
## `eas build:view [BUILD_ID]`
|
||||||
|
|
||||||
@@ -771,7 +772,7 @@ DESCRIPTION
|
|||||||
view a build for your project
|
view a build for your project
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/build/view.ts)_
|
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/view.ts)_
|
||||||
|
|
||||||
## `eas channel:create [NAME]`
|
## `eas channel:create [NAME]`
|
||||||
|
|
||||||
@@ -792,7 +793,7 @@ DESCRIPTION
|
|||||||
create a channel
|
create a channel
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/channel/create.ts)_
|
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/channel/create.ts)_
|
||||||
|
|
||||||
## `eas channel:delete [NAME]`
|
## `eas channel:delete [NAME]`
|
||||||
|
|
||||||
@@ -813,7 +814,7 @@ DESCRIPTION
|
|||||||
Delete a channel
|
Delete a channel
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/channel/delete.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/channel/delete.ts)_
|
_See code: [packages/eas-cli/src/commands/channel/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/channel/delete.ts)_
|
||||||
|
|
||||||
## `eas channel:edit [NAME]`
|
## `eas channel:edit [NAME]`
|
||||||
|
|
||||||
@@ -835,7 +836,7 @@ DESCRIPTION
|
|||||||
point a channel at a new branch
|
point a channel at a new branch
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/channel/edit.ts)_
|
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||||
|
|
||||||
## `eas channel:list`
|
## `eas channel:list`
|
||||||
|
|
||||||
@@ -855,7 +856,7 @@ DESCRIPTION
|
|||||||
list all channels
|
list all channels
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/channel/list.ts)_
|
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/channel/list.ts)_
|
||||||
|
|
||||||
## `eas channel:pause [NAME]`
|
## `eas channel:pause [NAME]`
|
||||||
|
|
||||||
@@ -877,7 +878,7 @@ DESCRIPTION
|
|||||||
pause a channel to stop it from sending updates
|
pause a channel to stop it from sending updates
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/channel/pause.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/channel/pause.ts)_
|
_See code: [packages/eas-cli/src/commands/channel/pause.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/channel/pause.ts)_
|
||||||
|
|
||||||
## `eas channel:resume [NAME]`
|
## `eas channel:resume [NAME]`
|
||||||
|
|
||||||
@@ -899,7 +900,7 @@ DESCRIPTION
|
|||||||
resume a channel to start sending updates
|
resume a channel to start sending updates
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/channel/resume.ts)_
|
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/channel/resume.ts)_
|
||||||
|
|
||||||
## `eas channel:rollout [CHANNEL]`
|
## `eas channel:rollout [CHANNEL]`
|
||||||
|
|
||||||
@@ -932,7 +933,7 @@ DESCRIPTION
|
|||||||
Roll a new branch out on a channel incrementally.
|
Roll a new branch out on a channel incrementally.
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/channel/rollout.ts)_
|
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/channel/rollout.ts)_
|
||||||
|
|
||||||
## `eas channel:view [NAME]`
|
## `eas channel:view [NAME]`
|
||||||
|
|
||||||
@@ -955,7 +956,7 @@ DESCRIPTION
|
|||||||
view a channel
|
view a channel
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/channel/view.ts)_
|
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/channel/view.ts)_
|
||||||
|
|
||||||
## `eas config`
|
## `eas config`
|
||||||
|
|
||||||
@@ -976,7 +977,7 @@ DESCRIPTION
|
|||||||
display project configuration (app.json + eas.json)
|
display project configuration (app.json + eas.json)
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/config.ts)_
|
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/config.ts)_
|
||||||
|
|
||||||
## `eas credentials`
|
## `eas credentials`
|
||||||
|
|
||||||
@@ -993,7 +994,7 @@ DESCRIPTION
|
|||||||
manage credentials
|
manage credentials
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/credentials/index.ts)_
|
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/credentials/index.ts)_
|
||||||
|
|
||||||
## `eas credentials:configure-build`
|
## `eas credentials:configure-build`
|
||||||
|
|
||||||
@@ -1011,7 +1012,7 @@ DESCRIPTION
|
|||||||
Set up credentials for building your project.
|
Set up credentials for building your project.
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
||||||
|
|
||||||
## `eas deploy [options]`
|
## `eas deploy [options]`
|
||||||
|
|
||||||
@@ -1039,7 +1040,7 @@ ALIASES
|
|||||||
$ eas worker:deploy
|
$ eas worker:deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/deploy/index.ts)_
|
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/deploy/index.ts)_
|
||||||
|
|
||||||
## `eas deploy:alias`
|
## `eas deploy:alias`
|
||||||
|
|
||||||
@@ -1064,7 +1065,7 @@ ALIASES
|
|||||||
$ eas deploy:promote
|
$ eas deploy:promote
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/deploy/alias/index.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/deploy/alias/index.ts)_
|
_See code: [packages/eas-cli/src/commands/deploy/alias/index.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/deploy/alias/index.ts)_
|
||||||
|
|
||||||
## `eas deploy:alias:delete [ALIAS_NAME]`
|
## `eas deploy:alias:delete [ALIAS_NAME]`
|
||||||
|
|
||||||
@@ -1085,7 +1086,7 @@ ALIASES
|
|||||||
$ eas worker:alias:delete
|
$ eas worker:alias:delete
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/deploy/alias/delete.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/deploy/alias/delete.ts)_
|
_See code: [packages/eas-cli/src/commands/deploy/alias/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/deploy/alias/delete.ts)_
|
||||||
|
|
||||||
## `eas deploy:delete [DEPLOYMENT_ID]`
|
## `eas deploy:delete [DEPLOYMENT_ID]`
|
||||||
|
|
||||||
@@ -1106,7 +1107,7 @@ ALIASES
|
|||||||
$ eas worker:delete
|
$ eas worker:delete
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/deploy/delete.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/deploy/delete.ts)_
|
_See code: [packages/eas-cli/src/commands/deploy/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/deploy/delete.ts)_
|
||||||
|
|
||||||
## `eas deploy:promote`
|
## `eas deploy:promote`
|
||||||
|
|
||||||
@@ -1143,7 +1144,7 @@ DESCRIPTION
|
|||||||
register new Apple Devices to use for internal distribution
|
register new Apple Devices to use for internal distribution
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/device/create.ts)_
|
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/device/create.ts)_
|
||||||
|
|
||||||
## `eas device:delete`
|
## `eas device:delete`
|
||||||
|
|
||||||
@@ -1163,7 +1164,7 @@ DESCRIPTION
|
|||||||
remove a registered device from your account
|
remove a registered device from your account
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/device/delete.ts)_
|
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/device/delete.ts)_
|
||||||
|
|
||||||
## `eas device:list`
|
## `eas device:list`
|
||||||
|
|
||||||
@@ -1184,7 +1185,7 @@ DESCRIPTION
|
|||||||
list all registered devices for your account
|
list all registered devices for your account
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/device/list.ts)_
|
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/device/list.ts)_
|
||||||
|
|
||||||
## `eas device:rename`
|
## `eas device:rename`
|
||||||
|
|
||||||
@@ -1205,7 +1206,7 @@ DESCRIPTION
|
|||||||
rename a registered device
|
rename a registered device
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/device/rename.ts)_
|
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/device/rename.ts)_
|
||||||
|
|
||||||
## `eas device:view [UDID]`
|
## `eas device:view [UDID]`
|
||||||
|
|
||||||
@@ -1219,7 +1220,7 @@ DESCRIPTION
|
|||||||
view a device for your project
|
view a device for your project
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/device/view.ts)_
|
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/device/view.ts)_
|
||||||
|
|
||||||
## `eas diagnostics`
|
## `eas diagnostics`
|
||||||
|
|
||||||
@@ -1233,7 +1234,7 @@ DESCRIPTION
|
|||||||
display environment info
|
display environment info
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||||
|
|
||||||
## `eas env:create [ENVIRONMENT]`
|
## `eas env:create [ENVIRONMENT]`
|
||||||
|
|
||||||
@@ -1263,7 +1264,7 @@ DESCRIPTION
|
|||||||
create an environment variable for the current project or account
|
create an environment variable for the current project or account
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/env/create.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/env/create.ts)_
|
_See code: [packages/eas-cli/src/commands/env/create.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/env/create.ts)_
|
||||||
|
|
||||||
## `eas env:delete [ENVIRONMENT]`
|
## `eas env:delete [ENVIRONMENT]`
|
||||||
|
|
||||||
@@ -1288,7 +1289,7 @@ DESCRIPTION
|
|||||||
delete an environment variable for the current project or account
|
delete an environment variable for the current project or account
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/env/delete.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/env/delete.ts)_
|
_See code: [packages/eas-cli/src/commands/env/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/env/delete.ts)_
|
||||||
|
|
||||||
## `eas env:exec ENVIRONMENT BASH_COMMAND`
|
## `eas env:exec ENVIRONMENT BASH_COMMAND`
|
||||||
|
|
||||||
@@ -1310,7 +1311,7 @@ DESCRIPTION
|
|||||||
execute a command with environment variables from the selected environment
|
execute a command with environment variables from the selected environment
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/env/exec.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/env/exec.ts)_
|
_See code: [packages/eas-cli/src/commands/env/exec.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/env/exec.ts)_
|
||||||
|
|
||||||
## `eas env:get [ENVIRONMENT]`
|
## `eas env:get [ENVIRONMENT]`
|
||||||
|
|
||||||
@@ -1335,7 +1336,7 @@ DESCRIPTION
|
|||||||
view an environment variable for the current project or account
|
view an environment variable for the current project or account
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/env/get.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/env/get.ts)_
|
_See code: [packages/eas-cli/src/commands/env/get.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/env/get.ts)_
|
||||||
|
|
||||||
## `eas env:list [ENVIRONMENT]`
|
## `eas env:list [ENVIRONMENT]`
|
||||||
|
|
||||||
@@ -1361,7 +1362,7 @@ DESCRIPTION
|
|||||||
list environment variables for the current project or account
|
list environment variables for the current project or account
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/env/list.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/env/list.ts)_
|
_See code: [packages/eas-cli/src/commands/env/list.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/env/list.ts)_
|
||||||
|
|
||||||
## `eas env:pull [ENVIRONMENT]`
|
## `eas env:pull [ENVIRONMENT]`
|
||||||
|
|
||||||
@@ -1383,7 +1384,7 @@ DESCRIPTION
|
|||||||
pull environment variables for the selected environment to .env file
|
pull environment variables for the selected environment to .env file
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/env/pull.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/env/pull.ts)_
|
_See code: [packages/eas-cli/src/commands/env/pull.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/env/pull.ts)_
|
||||||
|
|
||||||
## `eas env:push [ENVIRONMENT]`
|
## `eas env:push [ENVIRONMENT]`
|
||||||
|
|
||||||
@@ -1405,7 +1406,7 @@ DESCRIPTION
|
|||||||
push environment variables from .env file to the selected environment
|
push environment variables from .env file to the selected environment
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/env/push.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/env/push.ts)_
|
_See code: [packages/eas-cli/src/commands/env/push.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/env/push.ts)_
|
||||||
|
|
||||||
## `eas env:update [ENVIRONMENT]`
|
## `eas env:update [ENVIRONMENT]`
|
||||||
|
|
||||||
@@ -1437,7 +1438,7 @@ DESCRIPTION
|
|||||||
update an environment variable on the current project or account
|
update an environment variable on the current project or account
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/env/update.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/env/update.ts)_
|
_See code: [packages/eas-cli/src/commands/env/update.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/env/update.ts)_
|
||||||
|
|
||||||
## `eas fingerprint:compare [HASH1] [HASH2]`
|
## `eas fingerprint:compare [HASH1] [HASH2]`
|
||||||
|
|
||||||
@@ -1481,7 +1482,7 @@ EXAMPLES
|
|||||||
$ eas fingerprint:compare <FINGERPRINT-HASH> --update-id <UPDATE-ID> # Compare fingerprint from update against provided fingerprint
|
$ eas fingerprint:compare <FINGERPRINT-HASH> --update-id <UPDATE-ID> # Compare fingerprint from update against provided fingerprint
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/fingerprint/compare.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/fingerprint/compare.ts)_
|
_See code: [packages/eas-cli/src/commands/fingerprint/compare.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/fingerprint/compare.ts)_
|
||||||
|
|
||||||
## `eas fingerprint:generate`
|
## `eas fingerprint:generate`
|
||||||
|
|
||||||
@@ -1511,7 +1512,7 @@ EXAMPLES
|
|||||||
$ eas fingerprint:generate --json --non-interactive --platform android # Output fingerprint json to stdout
|
$ eas fingerprint:generate --json --non-interactive --platform android # Output fingerprint json to stdout
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/fingerprint/generate.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/fingerprint/generate.ts)_
|
_See code: [packages/eas-cli/src/commands/fingerprint/generate.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/fingerprint/generate.ts)_
|
||||||
|
|
||||||
## `eas help [COMMAND]`
|
## `eas help [COMMAND]`
|
||||||
|
|
||||||
@@ -1576,10 +1577,11 @@ log in with your Expo account
|
|||||||
|
|
||||||
```
|
```
|
||||||
USAGE
|
USAGE
|
||||||
$ eas login [-s]
|
$ eas login [-s] [-b]
|
||||||
|
|
||||||
FLAGS
|
FLAGS
|
||||||
-s, --sso Login with SSO
|
-b, --browser Login with your browser
|
||||||
|
-s, --sso Login with SSO
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
log in with your Expo account
|
log in with your Expo account
|
||||||
@@ -1619,7 +1621,7 @@ DESCRIPTION
|
|||||||
validate the local store configuration
|
validate the local store configuration
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/metadata/lint.ts)_
|
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||||
|
|
||||||
## `eas metadata:pull`
|
## `eas metadata:pull`
|
||||||
|
|
||||||
@@ -1636,7 +1638,7 @@ DESCRIPTION
|
|||||||
generate the local store configuration from the app stores
|
generate the local store configuration from the app stores
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/metadata/pull.ts)_
|
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||||
|
|
||||||
## `eas metadata:push`
|
## `eas metadata:push`
|
||||||
|
|
||||||
@@ -1653,7 +1655,7 @@ DESCRIPTION
|
|||||||
sync the local store configuration to the app stores
|
sync the local store configuration to the app stores
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/metadata/push.ts)_
|
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||||
|
|
||||||
## `eas new [PATH]`
|
## `eas new [PATH]`
|
||||||
|
|
||||||
@@ -1704,7 +1706,7 @@ DESCRIPTION
|
|||||||
open the project page in a web browser
|
open the project page in a web browser
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/open.ts)_
|
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/open.ts)_
|
||||||
|
|
||||||
## `eas project:info`
|
## `eas project:info`
|
||||||
|
|
||||||
@@ -1718,7 +1720,7 @@ DESCRIPTION
|
|||||||
information about the current project
|
information about the current project
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/project/info.ts)_
|
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/project/info.ts)_
|
||||||
|
|
||||||
## `eas project:init`
|
## `eas project:init`
|
||||||
|
|
||||||
@@ -1741,7 +1743,7 @@ ALIASES
|
|||||||
$ eas init
|
$ eas init
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/project/init.ts)_
|
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/project/init.ts)_
|
||||||
|
|
||||||
## `eas project:new [PATH]`
|
## `eas project:new [PATH]`
|
||||||
|
|
||||||
@@ -1764,7 +1766,7 @@ ALIASES
|
|||||||
$ eas new
|
$ eas new
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/project/new.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/project/new.ts)_
|
_See code: [packages/eas-cli/src/commands/project/new.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/project/new.ts)_
|
||||||
|
|
||||||
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
|
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||||
|
|
||||||
@@ -1782,7 +1784,7 @@ ALIASES
|
|||||||
$ eas onboarding
|
$ eas onboarding
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/project/onboarding.ts)_
|
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/project/onboarding.ts)_
|
||||||
|
|
||||||
## `eas submit`
|
## `eas submit`
|
||||||
|
|
||||||
@@ -1817,7 +1819,7 @@ ALIASES
|
|||||||
$ eas build:submit
|
$ eas build:submit
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/submit.ts)_
|
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/submit.ts)_
|
||||||
|
|
||||||
## `eas update`
|
## `eas update`
|
||||||
|
|
||||||
@@ -1857,7 +1859,7 @@ DESCRIPTION
|
|||||||
publish an update group
|
publish an update group
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/update/index.ts)_
|
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/update/index.ts)_
|
||||||
|
|
||||||
## `eas update:configure`
|
## `eas update:configure`
|
||||||
|
|
||||||
@@ -1877,7 +1879,7 @@ DESCRIPTION
|
|||||||
configure the project to support EAS Update
|
configure the project to support EAS Update
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/update/configure.ts)_
|
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/update/configure.ts)_
|
||||||
|
|
||||||
## `eas update:delete GROUPID`
|
## `eas update:delete GROUPID`
|
||||||
|
|
||||||
@@ -1898,7 +1900,7 @@ DESCRIPTION
|
|||||||
delete all the updates in an update group
|
delete all the updates in an update group
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/update/delete.ts)_
|
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/update/delete.ts)_
|
||||||
|
|
||||||
## `eas update:edit [GROUPID]`
|
## `eas update:edit [GROUPID]`
|
||||||
|
|
||||||
@@ -1922,7 +1924,7 @@ DESCRIPTION
|
|||||||
edit all the updates in an update group
|
edit all the updates in an update group
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/edit.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/update/edit.ts)_
|
_See code: [packages/eas-cli/src/commands/update/edit.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/update/edit.ts)_
|
||||||
|
|
||||||
## `eas update:list`
|
## `eas update:list`
|
||||||
|
|
||||||
@@ -1947,7 +1949,7 @@ DESCRIPTION
|
|||||||
view the recent updates
|
view the recent updates
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/update/list.ts)_
|
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/update/list.ts)_
|
||||||
|
|
||||||
## `eas update:republish`
|
## `eas update:republish`
|
||||||
|
|
||||||
@@ -1983,7 +1985,7 @@ DESCRIPTION
|
|||||||
roll back to an existing update
|
roll back to an existing update
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/update/republish.ts)_
|
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/update/republish.ts)_
|
||||||
|
|
||||||
## `eas update:revert-update-rollout`
|
## `eas update:revert-update-rollout`
|
||||||
|
|
||||||
@@ -2010,7 +2012,7 @@ DESCRIPTION
|
|||||||
revert a rollout update for a project
|
revert a rollout update for a project
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/revert-update-rollout.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/update/revert-update-rollout.ts)_
|
_See code: [packages/eas-cli/src/commands/update/revert-update-rollout.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/update/revert-update-rollout.ts)_
|
||||||
|
|
||||||
## `eas update:roll-back-to-embedded`
|
## `eas update:roll-back-to-embedded`
|
||||||
|
|
||||||
@@ -2038,7 +2040,7 @@ DESCRIPTION
|
|||||||
roll back to the embedded update
|
roll back to the embedded update
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
||||||
|
|
||||||
## `eas update:rollback`
|
## `eas update:rollback`
|
||||||
|
|
||||||
@@ -2059,7 +2061,7 @@ DESCRIPTION
|
|||||||
instead execute "eas update:republish" or "eas update:roll-back-to-embedded".
|
instead execute "eas update:republish" or "eas update:roll-back-to-embedded".
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/update/rollback.ts)_
|
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/update/rollback.ts)_
|
||||||
|
|
||||||
## `eas update:view GROUPID`
|
## `eas update:view GROUPID`
|
||||||
|
|
||||||
@@ -2079,7 +2081,7 @@ DESCRIPTION
|
|||||||
update group details
|
update group details
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/update/view.ts)_
|
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/update/view.ts)_
|
||||||
|
|
||||||
## `eas upload`
|
## `eas upload`
|
||||||
|
|
||||||
@@ -2100,7 +2102,7 @@ DESCRIPTION
|
|||||||
upload a local build and generate a sharable link
|
upload a local build and generate a sharable link
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/upload.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/upload.ts)_
|
_See code: [packages/eas-cli/src/commands/upload.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/upload.ts)_
|
||||||
|
|
||||||
## `eas webhook:create`
|
## `eas webhook:create`
|
||||||
|
|
||||||
@@ -2121,7 +2123,7 @@ DESCRIPTION
|
|||||||
create a webhook
|
create a webhook
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/webhook/create.ts)_
|
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||||
|
|
||||||
## `eas webhook:delete [ID]`
|
## `eas webhook:delete [ID]`
|
||||||
|
|
||||||
@@ -2141,7 +2143,7 @@ DESCRIPTION
|
|||||||
delete a webhook
|
delete a webhook
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/webhook/delete.ts)_
|
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||||
|
|
||||||
## `eas webhook:list`
|
## `eas webhook:list`
|
||||||
|
|
||||||
@@ -2159,7 +2161,7 @@ DESCRIPTION
|
|||||||
list webhooks
|
list webhooks
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/webhook/list.ts)_
|
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||||
|
|
||||||
## `eas webhook:update`
|
## `eas webhook:update`
|
||||||
|
|
||||||
@@ -2181,7 +2183,7 @@ DESCRIPTION
|
|||||||
update a webhook
|
update a webhook
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/webhook/update.ts)_
|
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||||
|
|
||||||
## `eas webhook:view ID`
|
## `eas webhook:view ID`
|
||||||
|
|
||||||
@@ -2198,7 +2200,7 @@ DESCRIPTION
|
|||||||
view a webhook
|
view a webhook
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/webhook/view.ts)_
|
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||||
|
|
||||||
## `eas whoami`
|
## `eas whoami`
|
||||||
|
|
||||||
@@ -2318,7 +2320,7 @@ DESCRIPTION
|
|||||||
to cancel.
|
to cancel.
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/workflow/cancel.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/workflow/cancel.ts)_
|
_See code: [packages/eas-cli/src/commands/workflow/cancel.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/workflow/cancel.ts)_
|
||||||
|
|
||||||
## `eas workflow:create [NAME]`
|
## `eas workflow:create [NAME]`
|
||||||
|
|
||||||
@@ -2338,7 +2340,7 @@ DESCRIPTION
|
|||||||
create a new workflow configuration YAML file
|
create a new workflow configuration YAML file
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/workflow/create.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/workflow/create.ts)_
|
_See code: [packages/eas-cli/src/commands/workflow/create.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/workflow/create.ts)_
|
||||||
|
|
||||||
## `eas workflow:logs [ID]`
|
## `eas workflow:logs [ID]`
|
||||||
|
|
||||||
@@ -2362,7 +2364,7 @@ DESCRIPTION
|
|||||||
If no ID is passed in, you will be prompted to select from recent workflow runs for the current project.
|
If no ID is passed in, you will be prompted to select from recent workflow runs for the current project.
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/workflow/logs.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/workflow/logs.ts)_
|
_See code: [packages/eas-cli/src/commands/workflow/logs.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/workflow/logs.ts)_
|
||||||
|
|
||||||
## `eas workflow:run [FILE]`
|
## `eas workflow:run [FILE]`
|
||||||
|
|
||||||
@@ -2402,7 +2404,7 @@ FLAG DESCRIPTIONS
|
|||||||
Exit codes: 0 = success, 11 = failure, 12 = canceled, 13 = wait aborted.
|
Exit codes: 0 = success, 11 = failure, 12 = canceled, 13 = wait aborted.
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/workflow/run.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/workflow/run.ts)_
|
_See code: [packages/eas-cli/src/commands/workflow/run.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/workflow/run.ts)_
|
||||||
|
|
||||||
## `eas workflow:runs`
|
## `eas workflow:runs`
|
||||||
|
|
||||||
@@ -2427,7 +2429,7 @@ DESCRIPTION
|
|||||||
list recent workflow runs for this project, with their IDs, statuses, and timestamps
|
list recent workflow runs for this project, with their IDs, statuses, and timestamps
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/workflow/runs.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/workflow/runs.ts)_
|
_See code: [packages/eas-cli/src/commands/workflow/runs.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/workflow/runs.ts)_
|
||||||
|
|
||||||
## `eas workflow:status [WORKFLOW_RUN_ID]`
|
## `eas workflow:status [WORKFLOW_RUN_ID]`
|
||||||
|
|
||||||
@@ -2455,7 +2457,7 @@ FLAG DESCRIPTIONS
|
|||||||
Exit codes: 0 = success, 11 = failure, 12 = canceled, 13 = wait aborted.
|
Exit codes: 0 = success, 11 = failure, 12 = canceled, 13 = wait aborted.
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/workflow/status.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/workflow/status.ts)_
|
_See code: [packages/eas-cli/src/commands/workflow/status.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/workflow/status.ts)_
|
||||||
|
|
||||||
## `eas workflow:validate PATH`
|
## `eas workflow:validate PATH`
|
||||||
|
|
||||||
@@ -2475,7 +2477,7 @@ DESCRIPTION
|
|||||||
validate a workflow configuration yaml file
|
validate a workflow configuration yaml file
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/workflow/validate.ts)_
|
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/workflow/validate.ts)_
|
||||||
|
|
||||||
## `eas workflow:view [ID]`
|
## `eas workflow:view [ID]`
|
||||||
|
|
||||||
@@ -2497,5 +2499,5 @@ DESCRIPTION
|
|||||||
workflow runs for the current project.
|
workflow runs for the current project.
|
||||||
```
|
```
|
||||||
|
|
||||||
_See code: [packages/eas-cli/src/commands/workflow/view.ts](https://github.com/expo/eas-cli/blob/v17.0.0/packages/eas-cli/src/commands/workflow/view.ts)_
|
_See code: [packages/eas-cli/src/commands/workflow/view.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/workflow/view.ts)_
|
||||||
<!-- commandsstop -->
|
<!-- commandsstop -->
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { Config } from '@jest/types';
|
import type { Config } from '@jest/types';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-relative-packages
|
|
||||||
import baseConfig from '../../jest/jest.shared.config';
|
import baseConfig from '../../jest/jest.shared.config';
|
||||||
|
|
||||||
const config: Config.InitialOptions = {
|
const config: Config.InitialOptions = {
|
||||||
|
|||||||
@@ -1,22 +1,57 @@
|
|||||||
{
|
{
|
||||||
"name": "eas-cli",
|
"name": "eas-cli",
|
||||||
|
"version": "18.0.4",
|
||||||
"description": "EAS command line tool",
|
"description": "EAS command line tool",
|
||||||
"version": "17.0.0",
|
"keywords": [
|
||||||
|
"cli",
|
||||||
|
"eas",
|
||||||
|
"expo"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/expo/eas-cli",
|
||||||
|
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||||
|
"license": "MIT",
|
||||||
"author": "Expo <[email protected]>",
|
"author": "Expo <[email protected]>",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/expo/eas-cli.git",
|
||||||
|
"directory": "packages/eas-cli"
|
||||||
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"eas": "./bin/run"
|
"eas": "./bin/run"
|
||||||
},
|
},
|
||||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
"files": [
|
||||||
|
"/bin",
|
||||||
|
"/build",
|
||||||
|
"/oclif.manifest.json",
|
||||||
|
"/schema"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"postpack": "rimraf oclif.manifest.json",
|
||||||
|
"prepack": "yarn rebuild && node ./scripts/prepack.js",
|
||||||
|
"rebuild": "rimraf build *.tsbuildinfo && yarn build",
|
||||||
|
"build": "yarn typecheck-for-build && yarn copy-new-templates",
|
||||||
|
"build-allow-unused": "tsc --project tsconfig.allowUnused.json",
|
||||||
|
"watch": "yarn typecheck-for-build --watch --preserveWatchOutput",
|
||||||
|
"watch-allow-unused": "yarn build-allow-unused --watch --preserveWatchOutput",
|
||||||
|
"typecheck": "tsc",
|
||||||
|
"typecheck-for-build": "tsc --project tsconfig.build.json",
|
||||||
|
"test": "jest",
|
||||||
|
"version": "yarn run -T oclif readme && node scripts/patch-readme && git add README.md",
|
||||||
|
"generate-graphql-code": "graphql-codegen --config graphql-codegen.yml",
|
||||||
|
"verify-graphql-code": "./scripts/verify-graphql.sh",
|
||||||
|
"clean": "rimraf dist build tmp node_modules yarn-error.log *.tsbuildinfo",
|
||||||
|
"copy-new-templates": "rimraf build/commandUtils/new/templates && mkdir -p build/commandUtils/new && cp -r src/commandUtils/new/templates build/commandUtils/new"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/apple-utils": "2.1.13",
|
"@expo/apple-utils": "2.1.13",
|
||||||
"@expo/code-signing-certificates": "0.0.5",
|
"@expo/code-signing-certificates": "0.0.5",
|
||||||
"@expo/config": "10.0.6",
|
"@expo/config": "10.0.6",
|
||||||
"@expo/config-plugins": "9.0.12",
|
"@expo/config-plugins": "9.0.12",
|
||||||
"@expo/eas-build-job": "17.0.0",
|
"@expo/eas-build-job": "18.0.2",
|
||||||
"@expo/eas-json": "17.0.0",
|
"@expo/eas-json": "18.0.2",
|
||||||
"@expo/env": "^1.0.0",
|
"@expo/env": "^1.0.0",
|
||||||
"@expo/json-file": "8.3.3",
|
"@expo/json-file": "8.3.3",
|
||||||
"@expo/logger": "17.0.0",
|
"@expo/logger": "18.0.1",
|
||||||
"@expo/multipart-body-parser": "2.0.0",
|
"@expo/multipart-body-parser": "2.0.0",
|
||||||
"@expo/osascript": "2.1.4",
|
"@expo/osascript": "2.1.4",
|
||||||
"@expo/package-manager": "1.9.10",
|
"@expo/package-manager": "1.9.10",
|
||||||
@@ -28,7 +63,7 @@
|
|||||||
"@expo/results": "1.0.0",
|
"@expo/results": "1.0.0",
|
||||||
"@expo/rudder-sdk-node": "1.1.1",
|
"@expo/rudder-sdk-node": "1.1.1",
|
||||||
"@expo/spawn-async": "1.7.2",
|
"@expo/spawn-async": "1.7.2",
|
||||||
"@expo/steps": "17.0.0",
|
"@expo/steps": "18.0.2",
|
||||||
"@expo/timeago.js": "1.0.0",
|
"@expo/timeago.js": "1.0.0",
|
||||||
"@oclif/core": "^1.26.2",
|
"@oclif/core": "^1.26.2",
|
||||||
"@oclif/plugin-autocomplete": "^2.3.10",
|
"@oclif/plugin-autocomplete": "^2.3.10",
|
||||||
@@ -123,6 +158,7 @@
|
|||||||
"axios": "1.11.0",
|
"axios": "1.11.0",
|
||||||
"eslint-plugin-graphql": "4.0.0",
|
"eslint-plugin-graphql": "4.0.0",
|
||||||
"express": "4.20.0",
|
"express": "4.20.0",
|
||||||
|
"jest": "29.7.0",
|
||||||
"memfs": "3.4.13",
|
"memfs": "3.4.13",
|
||||||
"mockdate": "3.0.5",
|
"mockdate": "3.0.5",
|
||||||
"nock": "13.4.0",
|
"nock": "13.4.0",
|
||||||
@@ -131,23 +167,13 @@
|
|||||||
"ts-node": "10.9.2",
|
"ts-node": "10.9.2",
|
||||||
"typescript": "5.5.4"
|
"typescript": "5.5.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
|
||||||
"node": ">=20.0.0"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"/bin",
|
|
||||||
"/build",
|
|
||||||
"/oclif.manifest.json",
|
|
||||||
"/schema"
|
|
||||||
],
|
|
||||||
"homepage": "https://github.com/expo/eas-cli",
|
|
||||||
"keywords": [
|
|
||||||
"expo",
|
|
||||||
"eas",
|
|
||||||
"cli"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"oclif": {
|
"oclif": {
|
||||||
|
"additionalHelpFlags": [
|
||||||
|
"-h"
|
||||||
|
],
|
||||||
|
"additionalVersionFlags": [
|
||||||
|
"-v"
|
||||||
|
],
|
||||||
"bin": "eas",
|
"bin": "eas",
|
||||||
"commands": "./build/commands",
|
"commands": "./build/commands",
|
||||||
"plugins": [
|
"plugins": [
|
||||||
@@ -171,9 +197,15 @@
|
|||||||
"device": {
|
"device": {
|
||||||
"description": "manage Apple devices for Internal Distribution"
|
"description": "manage Apple devices for Internal Distribution"
|
||||||
},
|
},
|
||||||
|
"env": {
|
||||||
|
"description": "manage project and account environment variables"
|
||||||
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"description": "manage store configuration"
|
"description": "manage store configuration"
|
||||||
},
|
},
|
||||||
|
"observe": {
|
||||||
|
"description": "monitor app performance metrics"
|
||||||
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"description": "manage project"
|
"description": "manage project"
|
||||||
},
|
},
|
||||||
@@ -183,9 +215,6 @@
|
|||||||
"secret": {
|
"secret": {
|
||||||
"description": "manage project and account secrets"
|
"description": "manage project and account secrets"
|
||||||
},
|
},
|
||||||
"env": {
|
|
||||||
"description": "manage project and account environment variables"
|
|
||||||
},
|
|
||||||
"update": {
|
"update": {
|
||||||
"description": "manage individual updates"
|
"description": "manage individual updates"
|
||||||
},
|
},
|
||||||
@@ -196,10 +225,6 @@
|
|||||||
"description": "manage workflows"
|
"description": "manage workflows"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"warn-if-update-available": {
|
|
||||||
"timeoutInDays": 0.5,
|
|
||||||
"message": "<%= chalk('★') %> <%= chalk.bold(config.name + '@' + latest) %> <%= chalk.bold('is now available.') %>\n<%= chalk.dim('To upgrade, run:') %>\n<%= chalk.dim(chalk.bold('npm install -g eas-cli')) %>\n<%= chalk.dim('Proceeding with outdated version.') %>\n"
|
|
||||||
},
|
|
||||||
"update": {
|
"update": {
|
||||||
"node": {
|
"node": {
|
||||||
"version": "20.19.4"
|
"version": "20.19.4"
|
||||||
@@ -213,34 +238,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalHelpFlags": [
|
"warn-if-update-available": {
|
||||||
"-h"
|
"message": "<%= chalk('★') %> <%= chalk.bold(config.name + '@' + latest) %> <%= chalk.bold('is now available.') %>\n<%= chalk.dim('To upgrade, run:') %>\n<%= chalk.dim(chalk.bold('npm install -g eas-cli')) %>\n<%= chalk.dim('Proceeding with outdated version.') %>\n",
|
||||||
],
|
"timeoutInDays": 0.5
|
||||||
"additionalVersionFlags": [
|
}
|
||||||
"-v"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"engines": {
|
||||||
"type": "git",
|
"node": ">=20.0.0"
|
||||||
"url": "https://github.com/expo/eas-cli.git",
|
|
||||||
"directory": "packages/eas-cli"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"postpack": "rimraf oclif.manifest.json",
|
|
||||||
"prepack": "yarn rebuild && node ./scripts/prepack.js",
|
|
||||||
"rebuild": "rimraf build && yarn build",
|
|
||||||
"pretarball-ci": "./scripts/pretarball-ci.sh",
|
|
||||||
"build": "yarn typecheck-for-build && yarn copy-new-templates",
|
|
||||||
"build-allow-unused": "tsc --project tsconfig.allowUnused.json",
|
|
||||||
"watch": "yarn typecheck-for-build --watch --preserveWatchOutput",
|
|
||||||
"watch-allow-unused": "yarn build-allow-unused --watch --preserveWatchOutput",
|
|
||||||
"typecheck": "tsc",
|
|
||||||
"typecheck-for-build": "tsc --project tsconfig.build.json",
|
|
||||||
"test": "jest",
|
|
||||||
"version": "yarn oclif readme && node scripts/patch-readme && git add README.md",
|
|
||||||
"generate-graphql-code": "graphql-codegen --config graphql-codegen.yml",
|
|
||||||
"verify-graphql-code": "./scripts/verify-graphql.sh",
|
|
||||||
"clean": "rimraf dist build tmp node_modules yarn-error.log *.tsbuildinfo",
|
|
||||||
"copy-new-templates": "rimraf build/commandUtils/new/templates && mkdir -p build/commandUtils/new && cp -r src/commandUtils/new/templates build/commandUtils/new"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,6 @@ const spawn = require('@expo/spawn-async');
|
|||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
if (!process.env.CLI_SIZE_CHECK) {
|
if (!process.env.CLI_SIZE_CHECK) {
|
||||||
await spawn('yarn', ['oclif', 'manifest'], { stdio: 'inherit' });
|
await spawn('yarn', ['run', '-T', 'oclif', 'manifest'], { stdio: 'inherit' });
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -eo pipefail
|
|
||||||
|
|
||||||
EAS_CLI_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
|
|
||||||
PACKAGE_JSON_PATH="$EAS_CLI_DIR/package.json"
|
|
||||||
|
|
||||||
# Define local packages and their directory names
|
|
||||||
# Only include packages that both exist in packages/ and are dependencies of the packages we care about
|
|
||||||
# We start with direct dependencies of eas-cli and their own local dependencies
|
|
||||||
PACKAGES=(
|
|
||||||
"@expo/eas-json:eas-json"
|
|
||||||
"@expo/eas-build-job:eas-build-job"
|
|
||||||
"@expo/logger:logger"
|
|
||||||
"@expo/steps:steps"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Function to rewrite dependencies in a package.json file
|
|
||||||
rewrite_deps() {
|
|
||||||
local PKG_JSON=$1
|
|
||||||
local IS_EAS_CLI=$2
|
|
||||||
|
|
||||||
for package in "${PACKAGES[@]}" ; do
|
|
||||||
KEY="${package%%:*}"
|
|
||||||
DIR="${package##*:}"
|
|
||||||
|
|
||||||
# Determine the relative path. If IS_EAS_CLI we go one level deeper because oclif builds in `tmp` in current directory.
|
|
||||||
if [[ "$IS_EAS_CLI" == "true" ]]; then
|
|
||||||
REL_PATH="file:../../../$DIR"
|
|
||||||
else
|
|
||||||
REL_PATH="file:../$DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Replace version with relative path
|
|
||||||
# Using # delimiter for sed to handle slashes
|
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
||||||
sed -i '' "s#\"$KEY\": \".*\"#\"$KEY\": \"$REL_PATH\"#g" "$PKG_JSON"
|
|
||||||
else
|
|
||||||
sed -i "s#\"$KEY\": \".*\"#\"$KEY\": \"$REL_PATH\"#g" "$PKG_JSON"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# Update eas-cli/package.json
|
|
||||||
echo "Updating eas-cli/package.json"
|
|
||||||
rewrite_deps "$PACKAGE_JSON_PATH" "true"
|
|
||||||
|
|
||||||
# Update other relevant local packages
|
|
||||||
# We iterate the PACKAGES list itself to find their locations and update them
|
|
||||||
# This ensures we only touch packages that are part of the dependency graph we care about
|
|
||||||
WORKSPACE_ROOT="$(cd "$EAS_CLI_DIR/.." && pwd)"
|
|
||||||
|
|
||||||
for package in "${PACKAGES[@]}" ; do
|
|
||||||
DIR="${package##*:}"
|
|
||||||
PKG_JSON="$WORKSPACE_ROOT/$DIR/package.json"
|
|
||||||
|
|
||||||
if [[ -f "$PKG_JSON" ]]; then
|
|
||||||
echo "Updating $DIR/package.json"
|
|
||||||
rewrite_deps "$PKG_JSON" "false"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
@@ -27,7 +27,7 @@ if cmp -s graphql.schema.json graphql.schema.json.bak; then
|
|||||||
else
|
else
|
||||||
echo "GraphQL schema has changed on the server."
|
echo "GraphQL schema has changed on the server."
|
||||||
echo "Building eas-cli and its dependencies"
|
echo "Building eas-cli and its dependencies"
|
||||||
yarn lerna run build --scope eas-cli --include-dependencies
|
yarn run -T lerna run build --scope eas-cli --include-dependencies
|
||||||
# If there are no errors, then the schema is backwards compatible
|
# If there are no errors, then the schema is backwards compatible
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
extends: ['universe/shared/typescript-analysis'],
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ['*.ts', '*.d.ts'],
|
|
||||||
parserOptions: {
|
|
||||||
project: './packages/eas-cli/tsconfig.json',
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/explicit-function-return-type': [
|
|
||||||
'warn',
|
|
||||||
{
|
|
||||||
allowExpressions: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'@typescript-eslint/prefer-nullish-coalescing': ['warn', { ignorePrimitives: true }],
|
|
||||||
'@typescript-eslint/no-confusing-void-expression': 'warn',
|
|
||||||
'@typescript-eslint/await-thenable': 'error',
|
|
||||||
'@typescript-eslint/no-misused-promises': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
checksVoidReturn: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'@typescript-eslint/no-floating-promises': 'error',
|
|
||||||
'no-void': ['warn', { allowAsStatement: true }],
|
|
||||||
'no-return-await': 'off',
|
|
||||||
'@typescript-eslint/return-await': ['error', 'always'],
|
|
||||||
'@typescript-eslint/no-confusing-non-null-assertion': 'warn',
|
|
||||||
'@typescript-eslint/no-extra-non-null-assertion': 'warn',
|
|
||||||
'@typescript-eslint/prefer-as-const': 'warn',
|
|
||||||
'@typescript-eslint/prefer-includes': 'warn',
|
|
||||||
'@typescript-eslint/prefer-readonly': 'warn',
|
|
||||||
'@typescript-eslint/prefer-string-starts-ends-with': 'warn',
|
|
||||||
'@typescript-eslint/prefer-ts-expect-error': 'warn',
|
|
||||||
'@typescript-eslint/no-unnecessary-type-assertion': 'warn',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"extends": ["../.oxlintrc.json"],
|
||||||
|
"jsPlugins": [
|
||||||
|
{
|
||||||
|
"name": "async-protect",
|
||||||
|
"specifier": "eslint-plugin-async-protect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "graphql",
|
||||||
|
"specifier": "eslint-plugin-graphql"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"typescript/explicit-function-return-type": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"allowExpressions": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"typescript/prefer-nullish-coalescing": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"ignorePrimitives": true,
|
||||||
|
"ignoreIfStatements": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"typescript/no-confusing-void-expression": "warn",
|
||||||
|
"typescript/await-thenable": "error",
|
||||||
|
"typescript/no-misused-promises": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"checksVoidReturn": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"typescript/no-floating-promises": "error",
|
||||||
|
"no-void": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"allowAsStatement": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-return-await": "off",
|
||||||
|
"typescript/return-await": ["warn", "error-handling-correctness-only"],
|
||||||
|
"typescript/no-confusing-non-null-assertion": "warn",
|
||||||
|
"typescript/no-extra-non-null-assertion": "warn",
|
||||||
|
"typescript/prefer-as-const": "warn",
|
||||||
|
"typescript/prefer-includes": "warn",
|
||||||
|
"typescript/prefer-ts-expect-error": "warn",
|
||||||
|
"typescript/no-unnecessary-type-assertion": "warn"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,8 +57,7 @@ interface BaseGetContextAsyncArgs {
|
|||||||
vcsClientOverride?: Client;
|
vcsClientOverride?: Client;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetContextAsyncArgsWithRequiredServerSideEnvironmentArgument
|
interface GetContextAsyncArgsWithRequiredServerSideEnvironmentArgument extends BaseGetContextAsyncArgs {
|
||||||
extends BaseGetContextAsyncArgs {
|
|
||||||
withServerSideEnvironment: string | null;
|
withServerSideEnvironment: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ describe(EasCommand.name, () => {
|
|||||||
|
|
||||||
const sessionManagerSpy = jest.spyOn(SessionManager.prototype, 'getUserAsync');
|
const sessionManagerSpy = jest.spyOn(SessionManager.prototype, 'getUserAsync');
|
||||||
expect(sessionManagerSpy).toBeCalledTimes(1);
|
expect(sessionManagerSpy).toBeCalledTimes(1);
|
||||||
}, 30_000);
|
}, 60_000);
|
||||||
|
|
||||||
it('initializes analytics', async () => {
|
it('initializes analytics', async () => {
|
||||||
const TestEasCommand = createTestEasCommand();
|
const TestEasCommand = createTestEasCommand();
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const EasJsonOnlyFlag = {
|
|||||||
export const EasUpdateEnvironmentFlag = {
|
export const EasUpdateEnvironmentFlag = {
|
||||||
environment: Flags.string({
|
environment: Flags.string({
|
||||||
description:
|
description:
|
||||||
'Environment to use for the server-side defined EAS environment variables during command execution, e.g. "production", "preview", "development"',
|
'Environment to use for the server-side defined EAS environment variables during command execution, e.g. "production", "preview", "development". Required for projects using Expo SDK 55 or greater.',
|
||||||
required: false,
|
required: false,
|
||||||
default: undefined,
|
default: undefined,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -153,7 +153,6 @@ If there are errors in **Expo Go** or the project is not running, create a **dev
|
|||||||
When working on this project:
|
When working on this project:
|
||||||
|
|
||||||
1. **Always start by consulting the appropriate documentation**:
|
1. **Always start by consulting the appropriate documentation**:
|
||||||
|
|
||||||
- For general Expo questions: https://docs.expo.dev/llms-full.txt
|
- For general Expo questions: https://docs.expo.dev/llms-full.txt
|
||||||
- For EAS/deployment questions: https://docs.expo.dev/llms-eas.txt
|
- For EAS/deployment questions: https://docs.expo.dev/llms-eas.txt
|
||||||
- For SDK/API questions: https://docs.expo.dev/llms-sdk.txt
|
- For SDK/API questions: https://docs.expo.dev/llms-sdk.txt
|
||||||
|
|||||||
@@ -35,12 +35,16 @@ const parseFlagInputStringAsInteger = (
|
|||||||
export const getLimitFlagWithCustomValues = ({
|
export const getLimitFlagWithCustomValues = ({
|
||||||
defaultTo,
|
defaultTo,
|
||||||
limit,
|
limit,
|
||||||
|
description,
|
||||||
}: {
|
}: {
|
||||||
defaultTo: number;
|
defaultTo: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
|
description?: string;
|
||||||
}): OptionFlag<number | undefined> =>
|
}): OptionFlag<number | undefined> =>
|
||||||
Flags.integer({
|
Flags.integer({
|
||||||
description: `The number of items to fetch each query. Defaults to ${defaultTo} and is capped at ${limit}.`,
|
description:
|
||||||
|
description ??
|
||||||
|
`The number of items to fetch each query. Defaults to ${defaultTo} and is capped at ${limit}.`,
|
||||||
// eslint-disable-next-line async-protect/async-suffix
|
// eslint-disable-next-line async-protect/async-suffix
|
||||||
parse: async input => parseFlagInputStringAsInteger(input, 'limit', 1, limit),
|
parse: async input => parseFlagInputStringAsInteger(input, 'limit', 1, limit),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ export default class Config extends EasCommand {
|
|||||||
maybeProfile ??
|
maybeProfile ??
|
||||||
(await selectAsync(
|
(await selectAsync(
|
||||||
'Select build profile',
|
'Select build profile',
|
||||||
(await EasJsonUtils.getBuildProfileNamesAsync(accessor)).map(profileName => ({
|
(
|
||||||
|
await EasJsonUtils.getBuildProfileNamesAsync(accessor)
|
||||||
|
).map(profileName => ({
|
||||||
title: profileName,
|
title: profileName,
|
||||||
value: profileName,
|
value: profileName,
|
||||||
}))
|
}))
|
||||||
|
|||||||
+5
-2
@@ -117,7 +117,10 @@ export default class EnvCreate extends EasCommand {
|
|||||||
force,
|
force,
|
||||||
type,
|
type,
|
||||||
fileName,
|
fileName,
|
||||||
} = await this.promptForMissingFlagsAsync(validatedFlags, args, { graphqlClient, projectId });
|
} = await this.promptForMissingFlagsAsync(validatedFlags, args, {
|
||||||
|
graphqlClient,
|
||||||
|
projectId,
|
||||||
|
});
|
||||||
|
|
||||||
const [projectDisplayName, ownerAccount] = await Promise.all([
|
const [projectDisplayName, ownerAccount] = await Promise.all([
|
||||||
getDisplayNameForProjectIdAsync(graphqlClient, projectId),
|
getDisplayNameForProjectIdAsync(graphqlClient, projectId),
|
||||||
@@ -323,7 +326,7 @@ export default class EnvCreate extends EasCommand {
|
|||||||
|
|
||||||
value = environmentFilePath ? await fs.readFile(environmentFilePath, 'base64') : value;
|
value = environmentFilePath ? await fs.readFile(environmentFilePath, 'base64') : value;
|
||||||
|
|
||||||
let newEnvironments = environments ? environments : environment ? [environment] : undefined;
|
let newEnvironments = environments ?? (environment ? [environment] : undefined);
|
||||||
|
|
||||||
if (!newEnvironments) {
|
if (!newEnvironments) {
|
||||||
newEnvironments = await promptVariableEnvironmentAsync({
|
newEnvironments = await promptVariableEnvironmentAsync({
|
||||||
|
|||||||
+1
-5
@@ -183,11 +183,7 @@ export default class EnvList extends EasCommand {
|
|||||||
flags: RawListFlags,
|
flags: RawListFlags,
|
||||||
{ environment }: { environment?: string }
|
{ environment }: { environment?: string }
|
||||||
): ListFlags {
|
): ListFlags {
|
||||||
const environments = flags.environment
|
const environments = flags.environment ?? (environment ? [environment] : undefined);
|
||||||
? flags.environment
|
|
||||||
: environment
|
|
||||||
? [environment]
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...flags,
|
...flags,
|
||||||
|
|||||||
@@ -0,0 +1,610 @@
|
|||||||
|
import { App, User, UserRole } from '@expo/apple-utils';
|
||||||
|
import spawnAsync from '@expo/spawn-async';
|
||||||
|
import { Flags } from '@oclif/core';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import * as fs from 'fs-extra';
|
||||||
|
import * as os from 'os';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { Analytics } from '../analytics/AnalyticsManager';
|
||||||
|
import EasCommand from '../commandUtils/EasCommand';
|
||||||
|
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||||
|
import { saveProjectIdToAppConfigAsync } from '../commandUtils/context/contextUtils/getProjectIdAsync';
|
||||||
|
import { CredentialsContext } from '../credentials/context';
|
||||||
|
import { AppStoreApiKeyPurpose } from '../credentials/ios/actions/AscApiKeyUtils';
|
||||||
|
import { getAppFromContextAsync } from '../credentials/ios/actions/BuildCredentialsUtils';
|
||||||
|
import { SetUpAscApiKey } from '../credentials/ios/actions/SetUpAscApiKey';
|
||||||
|
import { SetUpBuildCredentials } from '../credentials/ios/actions/SetUpBuildCredentials';
|
||||||
|
import { SetUpPushKey } from '../credentials/ios/actions/SetUpPushKey';
|
||||||
|
import { ensureAppExistsAsync } from '../credentials/ios/appstore/ensureAppExists';
|
||||||
|
import { Target } from '../credentials/ios/types';
|
||||||
|
import {
|
||||||
|
WorkflowJobStatus,
|
||||||
|
WorkflowProjectSourceType,
|
||||||
|
WorkflowRunStatus,
|
||||||
|
} from '../graphql/generated';
|
||||||
|
import { AppMutation } from '../graphql/mutations/AppMutation';
|
||||||
|
import { WorkflowRunMutation } from '../graphql/mutations/WorkflowRunMutation';
|
||||||
|
import { AppQuery } from '../graphql/queries/AppQuery';
|
||||||
|
import { WorkflowRunQuery } from '../graphql/queries/WorkflowRunQuery';
|
||||||
|
import Log, { learnMore } from '../log';
|
||||||
|
import { confirmAsync } from '../prompts';
|
||||||
|
import { ora } from '../ora';
|
||||||
|
import { getPrivateExpoConfigAsync } from '../project/expoConfig';
|
||||||
|
import { findProjectIdByAccountNameAndSlugNullableAsync } from '../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
||||||
|
import { uploadAccountScopedFileAsync } from '../project/uploadAccountScopedFileAsync';
|
||||||
|
import { uploadAccountScopedProjectSourceAsync } from '../project/uploadAccountScopedProjectSourceAsync';
|
||||||
|
import { Actor, getActorDisplayName } from '../user/User';
|
||||||
|
import { sleepAsync } from '../utils/promise';
|
||||||
|
import { resolveVcsClient } from '../vcs';
|
||||||
|
import { Client as VcsClient } from '../vcs/vcs';
|
||||||
|
|
||||||
|
// Expo Go release info - update when releasing a new version
|
||||||
|
const EXPO_GO_SDK_VERSION = '55';
|
||||||
|
const EXPO_GO_APP_VERSION = '55.0.11';
|
||||||
|
const EXPO_GO_BUILD_NUMBER = '1017799';
|
||||||
|
|
||||||
|
const TESTFLIGHT_GROUP_NAME = 'Team (Expo)';
|
||||||
|
|
||||||
|
async function setupTestFlightAsync(ascApp: App): Promise<void> {
|
||||||
|
// Create or get TestFlight group
|
||||||
|
let group;
|
||||||
|
for (let attempt = 0; attempt < 10; attempt++) {
|
||||||
|
try {
|
||||||
|
const groups = await ascApp.getBetaGroupsAsync({
|
||||||
|
query: { includes: ['betaTesters'] },
|
||||||
|
});
|
||||||
|
|
||||||
|
group = groups.find(
|
||||||
|
g => g.attributes.isInternalGroup && g.attributes.name === TESTFLIGHT_GROUP_NAME
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!group) {
|
||||||
|
group = await ascApp.createBetaGroupAsync({
|
||||||
|
name: TESTFLIGHT_GROUP_NAME,
|
||||||
|
isInternalGroup: true,
|
||||||
|
hasAccessToAllBuilds: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} catch (error: any) {
|
||||||
|
// Apple returns this error when the app isn't ready yet
|
||||||
|
if (error?.data?.errors?.some((e: any) => e.code === 'ENTITY_ERROR.RELATIONSHIP.INVALID')) {
|
||||||
|
if (attempt < 9) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10000));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!group) {
|
||||||
|
throw new Error('Failed to create TestFlight group');
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = await User.getAsync(ascApp.context);
|
||||||
|
const admins = users.filter(u => u.attributes.roles?.includes(UserRole.ADMIN));
|
||||||
|
|
||||||
|
const existingEmails = new Set(
|
||||||
|
group.attributes.betaTesters?.map((t: any) => t.attributes.email?.toLowerCase()) ?? []
|
||||||
|
);
|
||||||
|
|
||||||
|
const newTesters = admins
|
||||||
|
.filter(u => u.attributes.email && !existingEmails.has(u.attributes.email.toLowerCase()))
|
||||||
|
.map(u => ({
|
||||||
|
email: u.attributes.email!,
|
||||||
|
firstName: u.attributes.firstName ?? '',
|
||||||
|
lastName: u.attributes.lastName ?? '',
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (newTesters.length > 0) {
|
||||||
|
await group.createBulkBetaTesterAssignmentsAsync(newTesters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
async function withSuppressedOutputAsync<T>(fn: () => Promise<T>): Promise<T> {
|
||||||
|
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
||||||
|
const originalConsoleLog = console.log;
|
||||||
|
const originalConsoleError = console.error;
|
||||||
|
const originalConsoleWarn = console.warn;
|
||||||
|
|
||||||
|
let capturedOutput = '';
|
||||||
|
|
||||||
|
const capture = (chunk: any): boolean => {
|
||||||
|
if (typeof chunk === 'string') {
|
||||||
|
capturedOutput += chunk;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only suppress stdout, not stderr — ora writes spinner frames to stderr and
|
||||||
|
// patching it would freeze the spinner animation during suppressed async work.
|
||||||
|
process.stdout.write = capture as any;
|
||||||
|
console.log = () => {};
|
||||||
|
console.error = () => {};
|
||||||
|
console.warn = () => {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await fn();
|
||||||
|
} catch (error) {
|
||||||
|
process.stdout.write = originalStdoutWrite;
|
||||||
|
console.log = originalConsoleLog;
|
||||||
|
console.error = originalConsoleError;
|
||||||
|
console.warn = originalConsoleWarn;
|
||||||
|
|
||||||
|
if (capturedOutput) {
|
||||||
|
originalConsoleLog(capturedOutput);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
process.stdout.write = originalStdoutWrite;
|
||||||
|
console.log = originalConsoleLog;
|
||||||
|
console.error = originalConsoleError;
|
||||||
|
console.warn = originalConsoleWarn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* eslint-enable no-console */
|
||||||
|
|
||||||
|
export default class Go extends EasCommand {
|
||||||
|
static override description = 'Create a custom Expo Go and submit to TestFlight';
|
||||||
|
static override hidden = true;
|
||||||
|
|
||||||
|
static override flags = {
|
||||||
|
'bundle-id': Flags.string({
|
||||||
|
description: 'iOS bundle identifier (auto-generated if not provided)',
|
||||||
|
required: false,
|
||||||
|
}),
|
||||||
|
name: Flags.string({
|
||||||
|
description: 'App name',
|
||||||
|
default: 'My Expo Go',
|
||||||
|
}),
|
||||||
|
credentials: Flags.boolean({
|
||||||
|
description: 'Interactively select credentials (default: auto-select)',
|
||||||
|
default: false,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...this.ContextOptions.LoggedIn,
|
||||||
|
...this.ContextOptions.Analytics,
|
||||||
|
};
|
||||||
|
|
||||||
|
async runAsync(): Promise<void> {
|
||||||
|
Log.log(
|
||||||
|
chalk.bold(
|
||||||
|
`Creating your personal Expo Go and deploying to TestFlight. ${learnMore('https://expo.fyi/personal-expo-go')}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const { flags } = await this.parse(Go);
|
||||||
|
|
||||||
|
const spinner = ora('Logging in to Expo...').start();
|
||||||
|
const {
|
||||||
|
loggedIn: { actor, graphqlClient },
|
||||||
|
analytics,
|
||||||
|
} = await this.getContextAsync(Go, {
|
||||||
|
nonInteractive: false,
|
||||||
|
});
|
||||||
|
spinner.succeed(`Logged in as ${chalk.cyan(getActorDisplayName(actor))}`);
|
||||||
|
|
||||||
|
const bundleId = flags['bundle-id'] ?? this.generateBundleId(actor);
|
||||||
|
const appName = flags.name ?? 'My Expo Go';
|
||||||
|
const slug = bundleId.split('.').pop() || 'my-expo-go';
|
||||||
|
|
||||||
|
const projectDir = path.join(os.tmpdir(), `eas-go-${slug}`);
|
||||||
|
await fs.emptyDir(projectDir);
|
||||||
|
|
||||||
|
const originalCwd = process.cwd();
|
||||||
|
process.chdir(projectDir);
|
||||||
|
|
||||||
|
const setupSpinner = ora('Creating project...').start();
|
||||||
|
|
||||||
|
// Step 1: Create project files and initialize git (silently)
|
||||||
|
try {
|
||||||
|
await withSuppressedOutputAsync(async () => {
|
||||||
|
await this.createProjectFilesAsync(projectDir, bundleId, appName);
|
||||||
|
await this.initGitRepoAsync(projectDir);
|
||||||
|
});
|
||||||
|
const vcsClient = resolveVcsClient();
|
||||||
|
|
||||||
|
// Step 2: Create/link EAS project (silently)
|
||||||
|
const projectId = await withSuppressedOutputAsync(() =>
|
||||||
|
this.ensureEasProjectAsync(graphqlClient, actor, projectDir, bundleId)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 3: Set up iOS credentials and create App Store Connect app
|
||||||
|
const ascApp = await this.setupCredentialsAsync(
|
||||||
|
projectDir,
|
||||||
|
projectId,
|
||||||
|
bundleId,
|
||||||
|
appName,
|
||||||
|
graphqlClient,
|
||||||
|
actor,
|
||||||
|
analytics,
|
||||||
|
vcsClient,
|
||||||
|
flags.credentials,
|
||||||
|
() => {
|
||||||
|
setupSpinner.stop();
|
||||||
|
Log.markFreshLine();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 4: Start workflow and monitor progress
|
||||||
|
const { workflowUrl, workflowRunId } = await this.runWorkflowAsync(
|
||||||
|
graphqlClient,
|
||||||
|
projectDir,
|
||||||
|
projectId,
|
||||||
|
actor,
|
||||||
|
vcsClient
|
||||||
|
);
|
||||||
|
Log.withTick(`Workflow started: ${chalk.cyan(workflowUrl)}`);
|
||||||
|
|
||||||
|
const status = await this.monitorWorkflowJobsAsync(graphqlClient, workflowRunId);
|
||||||
|
if (status === WorkflowRunStatus.Failure) {
|
||||||
|
throw new Error('Workflow failed');
|
||||||
|
} else if (status === WorkflowRunStatus.Canceled) {
|
||||||
|
throw new Error('Workflow was canceled');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5: Set up TestFlight group (silently)
|
||||||
|
try {
|
||||||
|
await setupTestFlightAsync(ascApp);
|
||||||
|
} catch {
|
||||||
|
// Non-fatal: TestFlight group setup failure shouldn't block the user
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.newLine();
|
||||||
|
Log.succeed(
|
||||||
|
`Done! Your custom Expo Go has been submitted to TestFlight. ${learnMore(
|
||||||
|
`https://appstoreconnect.apple.com/apps/${ascApp.id}/testflight`,
|
||||||
|
{ learnMoreMessage: 'Open it on App Store Connect' }
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
Log.log(
|
||||||
|
`App Store processing may take several minutes to complete. ${learnMore(
|
||||||
|
'https://expo.fyi/personal-expo-go',
|
||||||
|
{ learnMoreMessage: 'Learn more about Expo Go on TestFlight' }
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
await fs.remove(projectDir);
|
||||||
|
} catch (error) {
|
||||||
|
Log.gray(`Project files preserved for debugging: ${projectDir}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
process.chdir(originalCwd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateBundleId(actor: Actor): string {
|
||||||
|
const username = actor.accounts[0].name;
|
||||||
|
// Sanitize username for bundle ID: only alphanumeric and hyphens allowed
|
||||||
|
const sanitizedUsername = username
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9-]/g, '')
|
||||||
|
.replace(/^-+|-+$/g, ''); // trim leading/trailing hyphens
|
||||||
|
// Deterministic bundle ID per user + SDK version (reuses same ASC app)
|
||||||
|
return `com.${sanitizedUsername || 'app'}.expogo${EXPO_GO_SDK_VERSION}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createProjectFilesAsync(
|
||||||
|
projectDir: string,
|
||||||
|
bundleId: string,
|
||||||
|
appName: string
|
||||||
|
): Promise<void> {
|
||||||
|
const slug = bundleId.split('.').pop() || 'custom-expo-go';
|
||||||
|
const extensionBundleId = `${bundleId}.ExpoNotificationServiceExtension`;
|
||||||
|
|
||||||
|
const appJson = {
|
||||||
|
expo: {
|
||||||
|
name: appName,
|
||||||
|
slug,
|
||||||
|
version: EXPO_GO_APP_VERSION,
|
||||||
|
ios: {
|
||||||
|
bundleIdentifier: bundleId,
|
||||||
|
buildNumber: EXPO_GO_BUILD_NUMBER,
|
||||||
|
config: {
|
||||||
|
usesNonExemptEncryption: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
eas: {
|
||||||
|
build: {
|
||||||
|
experimental: {
|
||||||
|
ios: {
|
||||||
|
appExtensions: [
|
||||||
|
{
|
||||||
|
targetName: 'ExpoNotificationServiceExtension',
|
||||||
|
bundleIdentifier: extensionBundleId,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const easJson = {
|
||||||
|
cli: {
|
||||||
|
version: '>= 5.0.0',
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
production: {
|
||||||
|
distribution: 'store',
|
||||||
|
credentialsSource: 'remote',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
production: {
|
||||||
|
ios: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const packageJson = {
|
||||||
|
name: slug,
|
||||||
|
version: '1.0.0',
|
||||||
|
dependencies: {
|
||||||
|
expo: '~54.0.0',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await fs.writeJson(path.join(projectDir, 'app.json'), appJson, { spaces: 2 });
|
||||||
|
await fs.writeJson(path.join(projectDir, 'eas.json'), easJson, { spaces: 2 });
|
||||||
|
await fs.writeJson(path.join(projectDir, 'package.json'), packageJson, { spaces: 2 });
|
||||||
|
|
||||||
|
await spawnAsync('npm', ['install'], { cwd: projectDir });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async initGitRepoAsync(projectDir: string): Promise<void> {
|
||||||
|
await spawnAsync('git', ['init'], { cwd: projectDir });
|
||||||
|
await spawnAsync('git', ['add', '.'], { cwd: projectDir });
|
||||||
|
await spawnAsync('git', ['commit', '-m', 'Initial commit'], { cwd: projectDir });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ensureEasProjectAsync(
|
||||||
|
graphqlClient: ExpoGraphqlClient,
|
||||||
|
actor: Actor,
|
||||||
|
projectDir: string,
|
||||||
|
bundleId: string
|
||||||
|
): Promise<string> {
|
||||||
|
const slug = bundleId.split('.').pop() || 'custom-expo-go';
|
||||||
|
const account = actor.accounts[0];
|
||||||
|
|
||||||
|
const existingProjectId = await findProjectIdByAccountNameAndSlugNullableAsync(
|
||||||
|
graphqlClient,
|
||||||
|
account.name,
|
||||||
|
slug
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingProjectId) {
|
||||||
|
await saveProjectIdToAppConfigAsync(projectDir, existingProjectId);
|
||||||
|
return existingProjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectId = await AppMutation.createAppAsync(graphqlClient, {
|
||||||
|
accountId: account.id,
|
||||||
|
projectName: slug,
|
||||||
|
});
|
||||||
|
await saveProjectIdToAppConfigAsync(projectDir, projectId);
|
||||||
|
return projectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async setupCredentialsAsync(
|
||||||
|
projectDir: string,
|
||||||
|
projectId: string,
|
||||||
|
bundleId: string,
|
||||||
|
appName: string,
|
||||||
|
graphqlClient: ExpoGraphqlClient,
|
||||||
|
actor: Actor,
|
||||||
|
analytics: Analytics,
|
||||||
|
vcsClient: VcsClient,
|
||||||
|
customizeCreds: boolean,
|
||||||
|
onBeforeAppleAuth?: () => void
|
||||||
|
): Promise<App> {
|
||||||
|
const exp = await getPrivateExpoConfigAsync(projectDir);
|
||||||
|
const extensionBundleId = `${bundleId}.ExpoNotificationServiceExtension`;
|
||||||
|
|
||||||
|
const credentialsCtx = new CredentialsContext({
|
||||||
|
projectInfo: { exp, projectId },
|
||||||
|
nonInteractive: false,
|
||||||
|
autoAcceptCredentialReuse: !customizeCreds,
|
||||||
|
projectDir,
|
||||||
|
user: actor,
|
||||||
|
graphqlClient,
|
||||||
|
analytics,
|
||||||
|
vcsClient,
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeAppleAuth?.();
|
||||||
|
const userAuthCtx = await credentialsCtx.appStore.ensureUserAuthenticatedAsync();
|
||||||
|
|
||||||
|
const app = await getAppFromContextAsync(credentialsCtx);
|
||||||
|
|
||||||
|
const targets: Target[] = [
|
||||||
|
{
|
||||||
|
targetName: exp.slug,
|
||||||
|
bundleIdentifier: bundleId,
|
||||||
|
entitlements: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targetName: 'ExpoNotificationServiceExtension',
|
||||||
|
bundleIdentifier: extensionBundleId,
|
||||||
|
parentBundleIdentifier: bundleId,
|
||||||
|
entitlements: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const ascApp = await withSuppressedOutputAsync(async () => {
|
||||||
|
await new SetUpBuildCredentials({
|
||||||
|
app,
|
||||||
|
targets,
|
||||||
|
distribution: 'store',
|
||||||
|
}).runAsync(credentialsCtx);
|
||||||
|
|
||||||
|
const appLookupParams = {
|
||||||
|
...app,
|
||||||
|
bundleIdentifier: bundleId,
|
||||||
|
};
|
||||||
|
await new SetUpAscApiKey(appLookupParams, AppStoreApiKeyPurpose.SUBMISSION_SERVICE).runAsync(
|
||||||
|
credentialsCtx
|
||||||
|
);
|
||||||
|
|
||||||
|
const ascAppResult = await ensureAppExistsAsync(userAuthCtx, {
|
||||||
|
name: appName,
|
||||||
|
bundleIdentifier: bundleId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const easJsonPath = path.join(projectDir, 'eas.json');
|
||||||
|
const easJson = await fs.readJson(easJsonPath);
|
||||||
|
easJson.submit = easJson.submit || {};
|
||||||
|
easJson.submit.production = easJson.submit.production || {};
|
||||||
|
easJson.submit.production.ios = easJson.submit.production.ios || {};
|
||||||
|
easJson.submit.production.ios.ascAppId = ascAppResult.id;
|
||||||
|
await fs.writeJson(easJsonPath, easJson, { spaces: 2 });
|
||||||
|
|
||||||
|
await spawnAsync('git', ['add', 'eas.json'], { cwd: projectDir });
|
||||||
|
await spawnAsync('git', ['commit', '-m', 'Add ascAppId to eas.json'], { cwd: projectDir });
|
||||||
|
|
||||||
|
return ascAppResult;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up push notifications (outside suppressed block so prompts are visible)
|
||||||
|
const appLookupParamsForPushKey = { ...app, bundleIdentifier: bundleId };
|
||||||
|
const setupPushKeyAction = new SetUpPushKey(appLookupParamsForPushKey);
|
||||||
|
const isPushKeySetup = await setupPushKeyAction.isPushKeySetupAsync(credentialsCtx);
|
||||||
|
if (!isPushKeySetup) {
|
||||||
|
if (customizeCreds) {
|
||||||
|
const wantsPushNotifications = await confirmAsync({
|
||||||
|
message: 'Would you like to set up Push Notifications for your app?',
|
||||||
|
initial: true,
|
||||||
|
});
|
||||||
|
if (wantsPushNotifications) {
|
||||||
|
await setupPushKeyAction.runAsync(credentialsCtx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await setupPushKeyAction.runAsync(credentialsCtx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ascApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async runWorkflowAsync(
|
||||||
|
graphqlClient: ExpoGraphqlClient,
|
||||||
|
projectDir: string,
|
||||||
|
projectId: string,
|
||||||
|
actor: Actor,
|
||||||
|
vcsClient: VcsClient
|
||||||
|
): Promise<{ workflowUrl: string; workflowRunId: string }> {
|
||||||
|
const account = actor.accounts[0];
|
||||||
|
|
||||||
|
const { projectArchiveBucketKey, easJsonBucketKey, packageJsonBucketKey } =
|
||||||
|
await withSuppressedOutputAsync(async () => {
|
||||||
|
const { projectArchiveBucketKey } = await uploadAccountScopedProjectSourceAsync({
|
||||||
|
graphqlClient,
|
||||||
|
vcsClient,
|
||||||
|
accountId: account.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { fileBucketKey: easJsonBucketKey } = await uploadAccountScopedFileAsync({
|
||||||
|
graphqlClient,
|
||||||
|
accountId: account.id,
|
||||||
|
filePath: path.join(projectDir, 'eas.json'),
|
||||||
|
maxSizeBytes: 1024 * 1024,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { fileBucketKey: packageJsonBucketKey } = await uploadAccountScopedFileAsync({
|
||||||
|
graphqlClient,
|
||||||
|
accountId: account.id,
|
||||||
|
filePath: path.join(projectDir, 'package.json'),
|
||||||
|
maxSizeBytes: 1024 * 1024,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { projectArchiveBucketKey, easJsonBucketKey, packageJsonBucketKey };
|
||||||
|
});
|
||||||
|
|
||||||
|
const { id: workflowRunId } = await WorkflowRunMutation.createExpoGoRepackWorkflowRunAsync(
|
||||||
|
graphqlClient,
|
||||||
|
{
|
||||||
|
appId: projectId,
|
||||||
|
projectSource: {
|
||||||
|
type: WorkflowProjectSourceType.Gcs,
|
||||||
|
projectArchiveBucketKey,
|
||||||
|
easJsonBucketKey,
|
||||||
|
packageJsonBucketKey,
|
||||||
|
projectRootDirectory: '.',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const app = await AppQuery.byIdAsync(graphqlClient, projectId);
|
||||||
|
const workflowUrl = `https://expo.dev/accounts/${account.name}/projects/${app.slug}/workflows/${workflowRunId}`;
|
||||||
|
|
||||||
|
return { workflowUrl, workflowRunId };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async monitorWorkflowJobsAsync(
|
||||||
|
graphqlClient: ExpoGraphqlClient,
|
||||||
|
workflowRunId: string
|
||||||
|
): Promise<WorkflowRunStatus> {
|
||||||
|
const buildSpinner = ora('Building Expo Go').start();
|
||||||
|
let submitSpinner: ReturnType<typeof ora> | null = null;
|
||||||
|
let buildCompleted = false;
|
||||||
|
let failedFetchesCount = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
const workflowRun = await WorkflowRunQuery.withJobsByIdAsync(graphqlClient, workflowRunId, {
|
||||||
|
useCache: false,
|
||||||
|
});
|
||||||
|
failedFetchesCount = 0;
|
||||||
|
|
||||||
|
const repackJob = workflowRun.jobs.find(j => j.name === 'Repack Expo Go');
|
||||||
|
const submitJob = workflowRun.jobs.find(j => j.name === 'Submit to TestFlight');
|
||||||
|
|
||||||
|
if (!buildCompleted) {
|
||||||
|
if (repackJob?.status === WorkflowJobStatus.Success) {
|
||||||
|
buildSpinner.succeed('Built Expo Go');
|
||||||
|
buildCompleted = true;
|
||||||
|
} else if (
|
||||||
|
repackJob?.status === WorkflowJobStatus.Failure ||
|
||||||
|
repackJob?.status === WorkflowJobStatus.Canceled
|
||||||
|
) {
|
||||||
|
buildSpinner.fail('Build failed');
|
||||||
|
return WorkflowRunStatus.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buildCompleted && submitSpinner === null && submitJob) {
|
||||||
|
submitSpinner = ora('Submitting to TestFlight').start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workflowRun.status === WorkflowRunStatus.Success) {
|
||||||
|
submitSpinner?.stop();
|
||||||
|
return WorkflowRunStatus.Success;
|
||||||
|
} else if (workflowRun.status === WorkflowRunStatus.Failure) {
|
||||||
|
buildSpinner.stop();
|
||||||
|
submitSpinner?.fail('Submission failed');
|
||||||
|
return WorkflowRunStatus.Failure;
|
||||||
|
} else if (workflowRun.status === WorkflowRunStatus.Canceled) {
|
||||||
|
buildSpinner.stop();
|
||||||
|
submitSpinner?.stop();
|
||||||
|
return WorkflowRunStatus.Canceled;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
failedFetchesCount++;
|
||||||
|
if (failedFetchesCount > 6) {
|
||||||
|
buildSpinner.fail();
|
||||||
|
submitSpinner?.fail();
|
||||||
|
throw new Error('Failed to fetch the workflow run status 6 times in a row');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleepAsync(10 * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
import { Config } from '@oclif/core';
|
||||||
|
|
||||||
|
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||||
|
import { AppObservePlatform } from '../../../graphql/generated';
|
||||||
|
import { fetchObserveEventsAsync } from '../../../observe/fetchEvents';
|
||||||
|
import { buildObserveEventsJson } from '../../../observe/formatEvents';
|
||||||
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../../utils/json';
|
||||||
|
import ObserveEvents from '../events';
|
||||||
|
|
||||||
|
jest.mock('../../../observe/fetchEvents');
|
||||||
|
jest.mock('../../../observe/formatEvents', () => ({
|
||||||
|
buildObserveEventsTable: jest.fn().mockReturnValue('table'),
|
||||||
|
buildObserveEventsJson: jest.fn().mockReturnValue({}),
|
||||||
|
}));
|
||||||
|
jest.mock('../../../log');
|
||||||
|
jest.mock('../../../utils/json');
|
||||||
|
|
||||||
|
const mockFetchObserveEventsAsync = jest.mocked(fetchObserveEventsAsync);
|
||||||
|
const mockBuildObserveEventsJson = jest.mocked(buildObserveEventsJson);
|
||||||
|
const mockEnableJsonOutput = jest.mocked(enableJsonOutput);
|
||||||
|
const mockPrintJsonOnlyOutput = jest.mocked(printJsonOnlyOutput);
|
||||||
|
|
||||||
|
describe(ObserveEvents, () => {
|
||||||
|
const graphqlClient = {} as any as ExpoGraphqlClient;
|
||||||
|
const mockConfig = {} as unknown as Config;
|
||||||
|
const projectId = 'test-project-id';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
mockFetchObserveEventsAsync.mockResolvedValue({
|
||||||
|
events: [],
|
||||||
|
pageInfo: { hasNextPage: false, hasPreviousPage: false },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createCommand(argv: string[]): ObserveEvents {
|
||||||
|
const command = new ObserveEvents(argv, mockConfig);
|
||||||
|
// @ts-expect-error
|
||||||
|
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||||
|
projectId,
|
||||||
|
loggedIn: { graphqlClient },
|
||||||
|
});
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('uses --days-from-now to compute start/end time range', async () => {
|
||||||
|
const now = new Date('2025-06-15T12:00:00.000Z');
|
||||||
|
jest.useFakeTimers({ now });
|
||||||
|
|
||||||
|
const command = createCommand(['--metric', 'tti', '--days-from-now', '7']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
expect(mockFetchObserveEventsAsync).toHaveBeenCalledTimes(1);
|
||||||
|
const options = mockFetchObserveEventsAsync.mock.calls[0][2];
|
||||||
|
expect(options.endTime).toBe('2025-06-15T12:00:00.000Z');
|
||||||
|
expect(options.startTime).toBe('2025-06-08T12:00:00.000Z');
|
||||||
|
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses DEFAULT_DAYS_BACK (60 days) when neither --days-from-now nor --start/--end are provided', async () => {
|
||||||
|
const now = new Date('2025-06-15T12:00:00.000Z');
|
||||||
|
jest.useFakeTimers({ now });
|
||||||
|
|
||||||
|
const command = createCommand(['--metric', 'tti']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
const options = mockFetchObserveEventsAsync.mock.calls[0][2];
|
||||||
|
expect(options.startTime).toBe('2025-04-16T12:00:00.000Z');
|
||||||
|
expect(options.endTime).toBe('2025-06-15T12:00:00.000Z');
|
||||||
|
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses explicit --start and --end when provided', async () => {
|
||||||
|
const command = createCommand([
|
||||||
|
'--metric',
|
||||||
|
'tti',
|
||||||
|
'--start',
|
||||||
|
'2025-01-01T00:00:00.000Z',
|
||||||
|
'--end',
|
||||||
|
'2025-02-01T00:00:00.000Z',
|
||||||
|
]);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
const options = mockFetchObserveEventsAsync.mock.calls[0][2];
|
||||||
|
expect(options.startTime).toBe('2025-01-01T00:00:00.000Z');
|
||||||
|
expect(options.endTime).toBe('2025-02-01T00:00:00.000Z');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults endTime to now when only --start is provided', async () => {
|
||||||
|
const now = new Date('2025-06-15T12:00:00.000Z');
|
||||||
|
jest.useFakeTimers({ now });
|
||||||
|
|
||||||
|
const command = createCommand(['--metric', 'tti', '--start', '2025-01-01T00:00:00.000Z']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
const options = mockFetchObserveEventsAsync.mock.calls[0][2];
|
||||||
|
expect(options.startTime).toBe('2025-01-01T00:00:00.000Z');
|
||||||
|
expect(options.endTime).toBe('2025-06-15T12:00:00.000Z');
|
||||||
|
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects --days-from-now combined with --start', async () => {
|
||||||
|
const command = createCommand([
|
||||||
|
'--metric',
|
||||||
|
'tti',
|
||||||
|
'--days-from-now',
|
||||||
|
'7',
|
||||||
|
'--start',
|
||||||
|
'2025-01-01T00:00:00.000Z',
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(command.runAsync()).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes --limit to fetchObserveEventsAsync', async () => {
|
||||||
|
const command = createCommand(['--metric', 'tti', '--limit', '42']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
const options = mockFetchObserveEventsAsync.mock.calls[0][2];
|
||||||
|
expect(options.limit).toBe(42);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes --after cursor to fetchObserveEventsAsync', async () => {
|
||||||
|
const command = createCommand(['--metric', 'tti', '--after', 'cursor-xyz']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
const options = mockFetchObserveEventsAsync.mock.calls[0][2];
|
||||||
|
expect(options.after).toBe('cursor-xyz');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not pass after when --after flag is not provided', async () => {
|
||||||
|
const command = createCommand(['--metric', 'tti']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
const options = mockFetchObserveEventsAsync.mock.calls[0][2];
|
||||||
|
expect(options).not.toHaveProperty('after');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects --days-from-now combined with --end', async () => {
|
||||||
|
const command = createCommand([
|
||||||
|
'--metric',
|
||||||
|
'tti',
|
||||||
|
'--days-from-now',
|
||||||
|
'7',
|
||||||
|
'--end',
|
||||||
|
'2025-02-01T00:00:00.000Z',
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(command.runAsync()).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes --platform ios to fetchObserveEventsAsync as AppObservePlatform.Ios', async () => {
|
||||||
|
const command = createCommand(['--metric', 'tti', '--platform', 'ios']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
const options = mockFetchObserveEventsAsync.mock.calls[0][2];
|
||||||
|
expect(options.platform).toBe(AppObservePlatform.Ios);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes --platform android to fetchObserveEventsAsync as AppObservePlatform.Android', async () => {
|
||||||
|
const command = createCommand(['--metric', 'tti', '--platform', 'android']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
const options = mockFetchObserveEventsAsync.mock.calls[0][2];
|
||||||
|
expect(options.platform).toBe(AppObservePlatform.Android);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes --app-version to fetchObserveEventsAsync', async () => {
|
||||||
|
const command = createCommand(['--metric', 'tti', '--app-version', '2.1.0']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
const options = mockFetchObserveEventsAsync.mock.calls[0][2];
|
||||||
|
expect(options.appVersion).toBe('2.1.0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes --update-id to fetchObserveEventsAsync', async () => {
|
||||||
|
const command = createCommand(['--metric', 'tti', '--update-id', 'update-xyz']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
const options = mockFetchObserveEventsAsync.mock.calls[0][2];
|
||||||
|
expect(options.updateId).toBe('update-xyz');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not pass platform, appVersion, or updateId when flags are not provided', async () => {
|
||||||
|
const command = createCommand(['--metric', 'tti']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
const options = mockFetchObserveEventsAsync.mock.calls[0][2];
|
||||||
|
expect(options.platform).toBeUndefined();
|
||||||
|
expect(options.appVersion).toBeUndefined();
|
||||||
|
expect(options.updateId).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls enableJsonOutput and printJsonOnlyOutput when --json is provided', async () => {
|
||||||
|
const mockEvents = [
|
||||||
|
{
|
||||||
|
id: 'evt-1',
|
||||||
|
metricName: 'expo.app_startup.tti',
|
||||||
|
metricValue: 1.23,
|
||||||
|
timestamp: '2025-01-15T10:30:00.000Z',
|
||||||
|
appVersion: '1.0.0',
|
||||||
|
appBuildNumber: '42',
|
||||||
|
deviceModel: 'iPhone 15',
|
||||||
|
deviceOs: 'iOS',
|
||||||
|
deviceOsVersion: '17.0',
|
||||||
|
countryCode: 'US',
|
||||||
|
sessionId: 'session-1',
|
||||||
|
easClientId: 'client-1',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
mockFetchObserveEventsAsync.mockResolvedValue({
|
||||||
|
events: mockEvents as any,
|
||||||
|
pageInfo: { hasNextPage: false, hasPreviousPage: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
const command = createCommand(['--metric', 'tti', '--json', '--non-interactive']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
expect(mockEnableJsonOutput).toHaveBeenCalled();
|
||||||
|
expect(mockBuildObserveEventsJson).toHaveBeenCalledWith(
|
||||||
|
mockEvents,
|
||||||
|
expect.objectContaining({ hasNextPage: false })
|
||||||
|
);
|
||||||
|
expect(mockPrintJsonOnlyOutput).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not call enableJsonOutput when --json is not provided', async () => {
|
||||||
|
const command = createCommand(['--metric', 'tti']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
expect(mockEnableJsonOutput).not.toHaveBeenCalled();
|
||||||
|
expect(mockPrintJsonOnlyOutput).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
import { Config } from '@oclif/core';
|
||||||
|
|
||||||
|
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||||
|
import { AppPlatform } from '../../../graphql/generated';
|
||||||
|
import { fetchObserveMetricsAsync, validateDateFlag } from '../../../observe/fetchMetrics';
|
||||||
|
import { buildObserveMetricsJson, buildObserveMetricsTable } from '../../../observe/formatMetrics';
|
||||||
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../../utils/json';
|
||||||
|
import ObserveMetrics from '../metrics';
|
||||||
|
|
||||||
|
jest.mock('../../../observe/fetchMetrics', () => {
|
||||||
|
const actual = jest.requireActual('../../../observe/fetchMetrics');
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
fetchObserveMetricsAsync: jest.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
jest.mock('../../../observe/formatMetrics', () => ({
|
||||||
|
...jest.requireActual('../../../observe/formatMetrics'),
|
||||||
|
buildObserveMetricsTable: jest.fn().mockReturnValue('table'),
|
||||||
|
buildObserveMetricsJson: jest.fn().mockReturnValue([]),
|
||||||
|
}));
|
||||||
|
jest.mock('../../../log');
|
||||||
|
jest.mock('../../../utils/json');
|
||||||
|
|
||||||
|
const mockFetchObserveMetricsAsync = jest.mocked(fetchObserveMetricsAsync);
|
||||||
|
const mockBuildObserveMetricsTable = jest.mocked(buildObserveMetricsTable);
|
||||||
|
const mockBuildObserveMetricsJson = jest.mocked(buildObserveMetricsJson);
|
||||||
|
const mockEnableJsonOutput = jest.mocked(enableJsonOutput);
|
||||||
|
const mockPrintJsonOnlyOutput = jest.mocked(printJsonOnlyOutput);
|
||||||
|
|
||||||
|
describe(ObserveMetrics, () => {
|
||||||
|
const graphqlClient = {} as any as ExpoGraphqlClient;
|
||||||
|
const mockConfig = {} as unknown as Config;
|
||||||
|
const projectId = 'test-project-id';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
mockFetchObserveMetricsAsync.mockResolvedValue(new Map());
|
||||||
|
});
|
||||||
|
|
||||||
|
function createCommand(argv: string[]): ObserveMetrics {
|
||||||
|
const command = new ObserveMetrics(argv, mockConfig);
|
||||||
|
// @ts-expect-error getContextAsync is a protected method
|
||||||
|
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||||
|
projectId,
|
||||||
|
loggedIn: { graphqlClient },
|
||||||
|
});
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('fetches metrics with default parameters (both platforms)', async () => {
|
||||||
|
const now = new Date('2025-06-15T12:00:00.000Z');
|
||||||
|
jest.useFakeTimers({ now });
|
||||||
|
|
||||||
|
const command = createCommand([]);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
expect(mockFetchObserveMetricsAsync).toHaveBeenCalledTimes(1);
|
||||||
|
const platforms = mockFetchObserveMetricsAsync.mock.calls[0][3];
|
||||||
|
expect(platforms).toEqual([AppPlatform.Android, AppPlatform.Ios]);
|
||||||
|
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('queries only Android when --platform android is passed', async () => {
|
||||||
|
const command = createCommand(['--platform', 'android']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
const platforms = mockFetchObserveMetricsAsync.mock.calls[0][3];
|
||||||
|
expect(platforms).toEqual([AppPlatform.Android]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('queries only iOS when --platform ios is passed', async () => {
|
||||||
|
const command = createCommand(['--platform', 'ios']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
const platforms = mockFetchObserveMetricsAsync.mock.calls[0][3];
|
||||||
|
expect(platforms).toEqual([AppPlatform.Ios]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves --metric aliases before passing to fetchObserveMetricsAsync', async () => {
|
||||||
|
const command = createCommand(['--metric', 'tti', '--metric', 'cold_launch']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
const metricNames = mockFetchObserveMetricsAsync.mock.calls[0][2];
|
||||||
|
expect(metricNames).toEqual(['expo.app_startup.tti', 'expo.app_startup.cold_launch_time']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses default time range (60 days back) when no --start/--end flags', async () => {
|
||||||
|
const now = new Date('2025-06-15T12:00:00.000Z');
|
||||||
|
jest.useFakeTimers({ now });
|
||||||
|
|
||||||
|
const command = createCommand([]);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
const startTime = mockFetchObserveMetricsAsync.mock.calls[0][4];
|
||||||
|
const endTime = mockFetchObserveMetricsAsync.mock.calls[0][5];
|
||||||
|
expect(endTime).toBe('2025-06-15T12:00:00.000Z');
|
||||||
|
expect(startTime).toBe('2025-04-16T12:00:00.000Z');
|
||||||
|
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses explicit --start and --end when provided', async () => {
|
||||||
|
const command = createCommand([
|
||||||
|
'--start',
|
||||||
|
'2025-01-01T00:00:00.000Z',
|
||||||
|
'--end',
|
||||||
|
'2025-02-01T00:00:00.000Z',
|
||||||
|
]);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
const startTime = mockFetchObserveMetricsAsync.mock.calls[0][4];
|
||||||
|
const endTime = mockFetchObserveMetricsAsync.mock.calls[0][5];
|
||||||
|
expect(startTime).toBe('2025-01-01T00:00:00.000Z');
|
||||||
|
expect(endTime).toBe('2025-02-01T00:00:00.000Z');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes resolved --stat flags to buildObserveMetricsTable', async () => {
|
||||||
|
const command = createCommand(['--stat', 'p90', '--stat', 'count']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
expect(mockBuildObserveMetricsTable).toHaveBeenCalledWith(
|
||||||
|
expect.any(Map),
|
||||||
|
expect.any(Array),
|
||||||
|
['p90', 'eventCount']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deduplicates --stat flags that resolve to the same key', async () => {
|
||||||
|
const command = createCommand(['--stat', 'med', '--stat', 'median']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
expect(mockBuildObserveMetricsTable).toHaveBeenCalledWith(
|
||||||
|
expect.any(Map),
|
||||||
|
expect.any(Array),
|
||||||
|
['median']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses --days-from-now to compute start/end time range', async () => {
|
||||||
|
const now = new Date('2025-06-15T12:00:00.000Z');
|
||||||
|
jest.useFakeTimers({ now });
|
||||||
|
|
||||||
|
const command = createCommand(['--days-from-now', '7']);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
const startTime = mockFetchObserveMetricsAsync.mock.calls[0][4];
|
||||||
|
const endTime = mockFetchObserveMetricsAsync.mock.calls[0][5];
|
||||||
|
expect(endTime).toBe('2025-06-15T12:00:00.000Z');
|
||||||
|
expect(startTime).toBe('2025-06-08T12:00:00.000Z');
|
||||||
|
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects --days-from-now combined with --start', async () => {
|
||||||
|
const command = createCommand(['--days-from-now', '7', '--start', '2025-01-01T00:00:00.000Z']);
|
||||||
|
|
||||||
|
await expect(command.runAsync()).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects --days-from-now combined with --end', async () => {
|
||||||
|
const command = createCommand(['--days-from-now', '7', '--end', '2025-02-01T00:00:00.000Z']);
|
||||||
|
|
||||||
|
await expect(command.runAsync()).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses default stats when --stat is not provided', async () => {
|
||||||
|
const command = createCommand([]);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
expect(mockBuildObserveMetricsTable).toHaveBeenCalledWith(
|
||||||
|
expect.any(Map),
|
||||||
|
expect.any(Array),
|
||||||
|
['median', 'eventCount']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes resolved --stat flags to buildObserveMetricsJson when --json is used', async () => {
|
||||||
|
const command = createCommand([
|
||||||
|
'--json',
|
||||||
|
'--non-interactive',
|
||||||
|
'--stat',
|
||||||
|
'min',
|
||||||
|
'--stat',
|
||||||
|
'avg',
|
||||||
|
]);
|
||||||
|
await command.runAsync();
|
||||||
|
|
||||||
|
expect(mockEnableJsonOutput).toHaveBeenCalled();
|
||||||
|
expect(mockBuildObserveMetricsJson).toHaveBeenCalledWith(
|
||||||
|
expect.any(Map),
|
||||||
|
expect.any(Array),
|
||||||
|
['min', 'average']
|
||||||
|
);
|
||||||
|
expect(mockPrintJsonOnlyOutput).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(validateDateFlag, () => {
|
||||||
|
it('throws on invalid --start date', () => {
|
||||||
|
expect(() => validateDateFlag('not-a-date', '--start')).toThrow(
|
||||||
|
'Invalid --start date: "not-a-date"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on invalid --end date', () => {
|
||||||
|
expect(() => validateDateFlag('also-bad', '--end')).toThrow('Invalid --end date: "also-bad"');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts valid ISO date in --start', () => {
|
||||||
|
expect(() => validateDateFlag('2025-01-01', '--start')).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
import { Flags } from '@oclif/core';
|
||||||
|
|
||||||
|
import EasCommand from '../../commandUtils/EasCommand';
|
||||||
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
|
import { getLimitFlagWithCustomValues } from '../../commandUtils/pagination';
|
||||||
|
import { AppObservePlatform } from '../../graphql/generated';
|
||||||
|
import Log from '../../log';
|
||||||
|
import {
|
||||||
|
type EventsOrderPreset,
|
||||||
|
fetchObserveEventsAsync,
|
||||||
|
resolveOrderBy,
|
||||||
|
} from '../../observe/fetchEvents';
|
||||||
|
import { resolveMetricName } from '../../observe/metricNames';
|
||||||
|
import { validateDateFlag } from '../../observe/fetchMetrics';
|
||||||
|
import { buildObserveEventsJson, buildObserveEventsTable } from '../../observe/formatEvents';
|
||||||
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||||
|
|
||||||
|
const DEFAULT_EVENTS_LIMIT = 10;
|
||||||
|
const DEFAULT_DAYS_BACK = 60;
|
||||||
|
|
||||||
|
export default class ObserveEvents extends EasCommand {
|
||||||
|
static override description = 'display individual app performance events ordered by metric value';
|
||||||
|
|
||||||
|
static override flags = {
|
||||||
|
metric: Flags.string({
|
||||||
|
description:
|
||||||
|
'Metric to query (full name or alias: tti, ttr, cold_launch, warm_launch, bundle_load)',
|
||||||
|
required: true,
|
||||||
|
}),
|
||||||
|
sort: Flags.enum<EventsOrderPreset>({
|
||||||
|
description: 'Sort order for events',
|
||||||
|
options: ['slowest', 'fastest', 'newest', 'oldest'],
|
||||||
|
default: 'slowest',
|
||||||
|
}),
|
||||||
|
platform: Flags.enum<'android' | 'ios'>({
|
||||||
|
description: 'Filter by platform',
|
||||||
|
options: ['android', 'ios'],
|
||||||
|
}),
|
||||||
|
after: Flags.string({
|
||||||
|
description:
|
||||||
|
'Cursor for pagination. Use the endCursor from a previous query to fetch the next page.',
|
||||||
|
}),
|
||||||
|
limit: getLimitFlagWithCustomValues({ defaultTo: DEFAULT_EVENTS_LIMIT, limit: 100 }),
|
||||||
|
start: Flags.string({
|
||||||
|
description: 'Start of time range (ISO date)',
|
||||||
|
exclusive: ['days-from-now'],
|
||||||
|
}),
|
||||||
|
end: Flags.string({
|
||||||
|
description: 'End of time range (ISO date)',
|
||||||
|
exclusive: ['days-from-now'],
|
||||||
|
}),
|
||||||
|
'days-from-now': Flags.integer({
|
||||||
|
description: 'Show events from the last N days (mutually exclusive with --start/--end)',
|
||||||
|
min: 1,
|
||||||
|
exclusive: ['start', 'end'],
|
||||||
|
}),
|
||||||
|
'app-version': Flags.string({
|
||||||
|
description: 'Filter by app version',
|
||||||
|
}),
|
||||||
|
'update-id': Flags.string({
|
||||||
|
description: 'Filter by EAS update ID',
|
||||||
|
}),
|
||||||
|
...EasNonInteractiveAndJsonFlags,
|
||||||
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...this.ContextOptions.ProjectId,
|
||||||
|
...this.ContextOptions.LoggedIn,
|
||||||
|
};
|
||||||
|
|
||||||
|
async runAsync(): Promise<void> {
|
||||||
|
const { flags } = await this.parse(ObserveEvents);
|
||||||
|
const {
|
||||||
|
projectId,
|
||||||
|
loggedIn: { graphqlClient },
|
||||||
|
} = await this.getContextAsync(ObserveEvents, {
|
||||||
|
nonInteractive: flags['non-interactive'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (flags.json) {
|
||||||
|
enableJsonOutput();
|
||||||
|
} else {
|
||||||
|
Log.warn('EAS Observe is in preview and subject to breaking changes.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags.start) {
|
||||||
|
validateDateFlag(flags.start, '--start');
|
||||||
|
}
|
||||||
|
if (flags.end) {
|
||||||
|
validateDateFlag(flags.end, '--end');
|
||||||
|
}
|
||||||
|
|
||||||
|
const metricName = resolveMetricName(flags.metric);
|
||||||
|
const orderBy = resolveOrderBy(flags.sort);
|
||||||
|
|
||||||
|
let startTime: string;
|
||||||
|
let endTime: string;
|
||||||
|
|
||||||
|
if (flags['days-from-now']) {
|
||||||
|
endTime = new Date().toISOString();
|
||||||
|
startTime = new Date(Date.now() - flags['days-from-now'] * 24 * 60 * 60 * 1000).toISOString();
|
||||||
|
} else {
|
||||||
|
endTime = flags.end ?? new Date().toISOString();
|
||||||
|
startTime =
|
||||||
|
flags.start ?? new Date(Date.now() - DEFAULT_DAYS_BACK * 24 * 60 * 60 * 1000).toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const platform = flags.platform
|
||||||
|
? flags.platform === 'android'
|
||||||
|
? AppObservePlatform.Android
|
||||||
|
: AppObservePlatform.Ios
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const { events, pageInfo } = await fetchObserveEventsAsync(graphqlClient, projectId, {
|
||||||
|
metricName,
|
||||||
|
orderBy,
|
||||||
|
limit: flags.limit ?? DEFAULT_EVENTS_LIMIT,
|
||||||
|
...(flags.after && { after: flags.after }),
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
platform,
|
||||||
|
appVersion: flags['app-version'],
|
||||||
|
updateId: flags['update-id'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (flags.json) {
|
||||||
|
printJsonOnlyOutput(buildObserveEventsJson(events, pageInfo));
|
||||||
|
} else {
|
||||||
|
Log.addNewLineIfNone();
|
||||||
|
Log.log(buildObserveEventsTable(events, pageInfo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
import { Flags } from '@oclif/core';
|
||||||
|
|
||||||
|
import EasCommand from '../../commandUtils/EasCommand';
|
||||||
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
|
import { AppPlatform } from '../../graphql/generated';
|
||||||
|
import Log from '../../log';
|
||||||
|
import { fetchObserveMetricsAsync, validateDateFlag } from '../../observe/fetchMetrics';
|
||||||
|
import {
|
||||||
|
StatisticKey,
|
||||||
|
buildObserveMetricsJson,
|
||||||
|
buildObserveMetricsTable,
|
||||||
|
resolveStatKey,
|
||||||
|
} from '../../observe/formatMetrics';
|
||||||
|
import { resolveMetricName } from '../../observe/metricNames';
|
||||||
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||||
|
|
||||||
|
const DEFAULT_METRICS = [
|
||||||
|
'expo.app_startup.cold_launch_time',
|
||||||
|
'expo.app_startup.warm_launch_time',
|
||||||
|
'expo.app_startup.tti',
|
||||||
|
'expo.app_startup.ttr',
|
||||||
|
'expo.app_startup.bundle_load_time',
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEFAULT_DAYS_BACK = 60;
|
||||||
|
|
||||||
|
const DEFAULT_STATS_TABLE: StatisticKey[] = ['median', 'eventCount'];
|
||||||
|
const DEFAULT_STATS_JSON: StatisticKey[] = [
|
||||||
|
'min',
|
||||||
|
'median',
|
||||||
|
'max',
|
||||||
|
'average',
|
||||||
|
'p80',
|
||||||
|
'p90',
|
||||||
|
'p99',
|
||||||
|
'eventCount',
|
||||||
|
];
|
||||||
|
|
||||||
|
export default class ObserveMetrics extends EasCommand {
|
||||||
|
static override description = 'display app performance metrics grouped by app version';
|
||||||
|
|
||||||
|
static override flags = {
|
||||||
|
platform: Flags.enum<'android' | 'ios'>({
|
||||||
|
description: 'Filter by platform',
|
||||||
|
options: ['android', 'ios'],
|
||||||
|
}),
|
||||||
|
metric: Flags.string({
|
||||||
|
description:
|
||||||
|
'Metric name to display (can be specified multiple times). Supports aliases: tti, ttr, cold_launch, warm_launch, bundle_load',
|
||||||
|
multiple: true,
|
||||||
|
}),
|
||||||
|
stat: Flags.string({
|
||||||
|
description:
|
||||||
|
'Statistic to display per metric (can be specified multiple times). Options: min, max, med, avg, p80, p90, p99, count',
|
||||||
|
multiple: true,
|
||||||
|
}),
|
||||||
|
start: Flags.string({
|
||||||
|
description: 'Start of time range for metrics data (ISO date).',
|
||||||
|
exclusive: ['days-from-now'],
|
||||||
|
}),
|
||||||
|
end: Flags.string({
|
||||||
|
description: 'End of time range for metrics data (ISO date).',
|
||||||
|
exclusive: ['days-from-now'],
|
||||||
|
}),
|
||||||
|
'days-from-now': Flags.integer({
|
||||||
|
description: 'Show metrics from the last N days (mutually exclusive with --start/--end)',
|
||||||
|
min: 1,
|
||||||
|
exclusive: ['start', 'end'],
|
||||||
|
}),
|
||||||
|
...EasNonInteractiveAndJsonFlags,
|
||||||
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...this.ContextOptions.ProjectId,
|
||||||
|
...this.ContextOptions.LoggedIn,
|
||||||
|
};
|
||||||
|
|
||||||
|
async runAsync(): Promise<void> {
|
||||||
|
const { flags } = await this.parse(ObserveMetrics);
|
||||||
|
const {
|
||||||
|
projectId,
|
||||||
|
loggedIn: { graphqlClient },
|
||||||
|
} = await this.getContextAsync(ObserveMetrics, {
|
||||||
|
nonInteractive: flags['non-interactive'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (flags.json) {
|
||||||
|
enableJsonOutput();
|
||||||
|
} else {
|
||||||
|
Log.warn('EAS Observe is in preview and subject to breaking changes.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags.start) {
|
||||||
|
validateDateFlag(flags.start, '--start');
|
||||||
|
}
|
||||||
|
if (flags.end) {
|
||||||
|
validateDateFlag(flags.end, '--end');
|
||||||
|
}
|
||||||
|
|
||||||
|
const metricNames = flags.metric?.length
|
||||||
|
? flags.metric.map(resolveMetricName)
|
||||||
|
: DEFAULT_METRICS;
|
||||||
|
|
||||||
|
let startTime: string;
|
||||||
|
let endTime: string;
|
||||||
|
|
||||||
|
if (flags['days-from-now']) {
|
||||||
|
endTime = new Date().toISOString();
|
||||||
|
startTime = new Date(Date.now() - flags['days-from-now'] * 24 * 60 * 60 * 1000).toISOString();
|
||||||
|
} else {
|
||||||
|
endTime = flags.end ?? new Date().toISOString();
|
||||||
|
startTime =
|
||||||
|
flags.start ?? new Date(Date.now() - DEFAULT_DAYS_BACK * 24 * 60 * 60 * 1000).toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const platforms: AppPlatform[] = flags.platform
|
||||||
|
? [flags.platform === 'android' ? AppPlatform.Android : AppPlatform.Ios]
|
||||||
|
: [AppPlatform.Android, AppPlatform.Ios];
|
||||||
|
|
||||||
|
const metricsMap = await fetchObserveMetricsAsync(
|
||||||
|
graphqlClient,
|
||||||
|
projectId,
|
||||||
|
metricNames,
|
||||||
|
platforms,
|
||||||
|
startTime,
|
||||||
|
endTime
|
||||||
|
);
|
||||||
|
|
||||||
|
const argumentsStat = flags.stat?.length
|
||||||
|
? Array.from(new Set(flags.stat.map(resolveStatKey)))
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (flags.json) {
|
||||||
|
const stats: StatisticKey[] = argumentsStat ?? DEFAULT_STATS_JSON;
|
||||||
|
printJsonOnlyOutput(buildObserveMetricsJson(metricsMap, metricNames, stats));
|
||||||
|
} else {
|
||||||
|
const stats: StatisticKey[] = argumentsStat ?? DEFAULT_STATS_TABLE;
|
||||||
|
Log.addNewLineIfNone();
|
||||||
|
Log.log(buildObserveMetricsTable(metricsMap, metricNames, stats));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -143,6 +143,67 @@ describe(UpdatePublish.name, () => {
|
|||||||
expect(PublishMutation.publishUpdateGroupAsync).toHaveBeenCalled();
|
expect(PublishMutation.publishUpdateGroupAsync).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('errors when SDK >= 55 and --environment is not provided', async () => {
|
||||||
|
const flags = ['--non-interactive', '--branch=branch123', '--message=abc'];
|
||||||
|
|
||||||
|
mockTestProject({ expoConfig: { sdkVersion: '55.0.0' } });
|
||||||
|
|
||||||
|
await expect(new UpdatePublish(flags, commandOptions).run()).rejects.toThrow(
|
||||||
|
'--environment flag is required for projects using Expo SDK 55 or greater'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not error when SDK >= 55 and --environment is provided', async () => {
|
||||||
|
const flags = [
|
||||||
|
'--non-interactive',
|
||||||
|
'--branch=branch123',
|
||||||
|
'--message=abc',
|
||||||
|
'--environment=production',
|
||||||
|
];
|
||||||
|
|
||||||
|
mockTestProject({ expoConfig: { sdkVersion: '55.0.0' } });
|
||||||
|
const { platforms, runtimeVersion } = mockTestExport();
|
||||||
|
|
||||||
|
jest.mocked(ensureBranchExistsAsync).mockResolvedValue({
|
||||||
|
branch: {
|
||||||
|
id: 'branch123',
|
||||||
|
name: 'wat',
|
||||||
|
},
|
||||||
|
createdBranch: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
jest
|
||||||
|
.mocked(PublishMutation.publishUpdateGroupAsync)
|
||||||
|
.mockResolvedValue(platforms.map(platform => ({ ...updateStub, platform, runtimeVersion })));
|
||||||
|
|
||||||
|
await new UpdatePublish(flags, commandOptions).run();
|
||||||
|
|
||||||
|
expect(PublishMutation.publishUpdateGroupAsync).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not error when SDK < 55 and --environment is not provided', async () => {
|
||||||
|
const flags = ['--non-interactive', '--branch=branch123', '--message=abc'];
|
||||||
|
|
||||||
|
mockTestProject({ expoConfig: { sdkVersion: '54.0.0' } });
|
||||||
|
const { platforms, runtimeVersion } = mockTestExport();
|
||||||
|
|
||||||
|
jest.mocked(ensureBranchExistsAsync).mockResolvedValue({
|
||||||
|
branch: {
|
||||||
|
id: 'branch123',
|
||||||
|
name: 'wat',
|
||||||
|
},
|
||||||
|
createdBranch: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
jest
|
||||||
|
.mocked(PublishMutation.publishUpdateGroupAsync)
|
||||||
|
.mockResolvedValue(platforms.map(platform => ({ ...updateStub, platform, runtimeVersion })));
|
||||||
|
|
||||||
|
await new UpdatePublish(flags, commandOptions).run();
|
||||||
|
|
||||||
|
expect(PublishMutation.publishUpdateGroupAsync).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('creates a new update with the public expo config', async () => {
|
it('creates a new update with the public expo config', async () => {
|
||||||
const flags = ['--non-interactive', '--branch=branch123', '--message=abc'];
|
const flags = ['--non-interactive', '--branch=branch123', '--message=abc'];
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
ensureEASUpdateIsConfiguredAsync,
|
ensureEASUpdateIsConfiguredAsync,
|
||||||
ensureEASUpdateIsConfiguredInEasJsonAsync,
|
ensureEASUpdateIsConfiguredInEasJsonAsync,
|
||||||
} from '../../update/configure';
|
} from '../../update/configure';
|
||||||
|
import { assertEnvironmentFlagForSdk55OrGreater } from '../../update/utils';
|
||||||
|
|
||||||
export default class UpdateConfigure extends EasCommand {
|
export default class UpdateConfigure extends EasCommand {
|
||||||
static override description = 'configure the project to support EAS Update';
|
static override description = 'configure the project to support EAS Update';
|
||||||
@@ -42,6 +43,11 @@ export default class UpdateConfigure extends EasCommand {
|
|||||||
withServerSideEnvironment: flags['environment'] ?? null,
|
withServerSideEnvironment: flags['environment'] ?? null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
assertEnvironmentFlagForSdk55OrGreater({
|
||||||
|
sdkVersion: exp.sdkVersion,
|
||||||
|
environment: flags['environment'],
|
||||||
|
});
|
||||||
|
|
||||||
Log.log(
|
Log.log(
|
||||||
'💡 The following process will configure your project to use EAS Update. These changes only apply to your local project files and you can safely revert them at any time.'
|
'💡 The following process will configure your project to use EAS Update. These changes only apply to your local project files and you can safely revert them at any time.'
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { ensureRepoIsCleanAsync } from '../../build/utils/repository';
|
|||||||
import { getUpdateGroupUrl } from '../../build/utils/url';
|
import { getUpdateGroupUrl } from '../../build/utils/url';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand from '../../commandUtils/EasCommand';
|
||||||
import { EasNonInteractiveAndJsonFlags, EasUpdateEnvironmentFlag } from '../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags, EasUpdateEnvironmentFlag } from '../../commandUtils/flags';
|
||||||
|
import { assertEnvironmentFlagForSdk55OrGreater } from '../../update/utils';
|
||||||
import { getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
import { getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
||||||
import fetch from '../../fetch';
|
import fetch from '../../fetch';
|
||||||
import {
|
import {
|
||||||
@@ -249,6 +250,11 @@ export default class UpdatePublish extends EasCommand {
|
|||||||
projectDir,
|
projectDir,
|
||||||
} = await getDynamicPublicProjectConfigAsync();
|
} = await getDynamicPublicProjectConfigAsync();
|
||||||
|
|
||||||
|
assertEnvironmentFlagForSdk55OrGreater({
|
||||||
|
sdkVersion: expPossiblyWithoutEasUpdateConfigured.sdkVersion,
|
||||||
|
environment,
|
||||||
|
});
|
||||||
|
|
||||||
await maybeWarnAboutEasOutagesAsync(graphqlClient, [StatuspageServiceName.EasUpdate]);
|
await maybeWarnAboutEasOutagesAsync(graphqlClient, [StatuspageServiceName.EasUpdate]);
|
||||||
|
|
||||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||||
@@ -468,13 +474,13 @@ export default class UpdatePublish extends EasCommand {
|
|||||||
return {
|
return {
|
||||||
...info,
|
...info,
|
||||||
expoUpdatesRuntimeFingerprintSource: info.expoUpdatesRuntimeFingerprint
|
expoUpdatesRuntimeFingerprintSource: info.expoUpdatesRuntimeFingerprint
|
||||||
? (
|
? ((
|
||||||
await maybeUploadFingerprintAsync({
|
await maybeUploadFingerprintAsync({
|
||||||
hash: info.runtimeVersion,
|
hash: info.runtimeVersion,
|
||||||
fingerprint: info.expoUpdatesRuntimeFingerprint,
|
fingerprint: info.expoUpdatesRuntimeFingerprint,
|
||||||
graphqlClient,
|
graphqlClient,
|
||||||
})
|
})
|
||||||
).fingerprintSource ?? null
|
).fingerprintSource ?? null)
|
||||||
: null,
|
: null,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export class CredentialsContext {
|
|||||||
public readonly appStore = new AppStoreApi();
|
public readonly appStore = new AppStoreApi();
|
||||||
public readonly ios = IosGraphqlClient;
|
public readonly ios = IosGraphqlClient;
|
||||||
public readonly nonInteractive: boolean;
|
public readonly nonInteractive: boolean;
|
||||||
|
public readonly autoAcceptCredentialReuse: boolean;
|
||||||
public readonly freezeCredentials: boolean = false;
|
public readonly freezeCredentials: boolean = false;
|
||||||
public readonly projectDir: string;
|
public readonly projectDir: string;
|
||||||
public readonly user: Actor;
|
public readonly user: Actor;
|
||||||
@@ -50,6 +51,7 @@ export class CredentialsContext {
|
|||||||
analytics: Analytics;
|
analytics: Analytics;
|
||||||
vcsClient: Client;
|
vcsClient: Client;
|
||||||
freezeCredentials?: boolean;
|
freezeCredentials?: boolean;
|
||||||
|
autoAcceptCredentialReuse?: boolean;
|
||||||
env?: Env;
|
env?: Env;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
@@ -60,6 +62,7 @@ export class CredentialsContext {
|
|||||||
this.analytics = options.analytics;
|
this.analytics = options.analytics;
|
||||||
this.vcsClient = options.vcsClient;
|
this.vcsClient = options.vcsClient;
|
||||||
this.nonInteractive = options.nonInteractive ?? false;
|
this.nonInteractive = options.nonInteractive ?? false;
|
||||||
|
this.autoAcceptCredentialReuse = options.autoAcceptCredentialReuse ?? false;
|
||||||
this.projectInfo = options.projectInfo;
|
this.projectInfo = options.projectInfo;
|
||||||
this.freezeCredentials = options.freezeCredentials ?? false;
|
this.freezeCredentials = options.freezeCredentials ?? false;
|
||||||
this.usesBroadcastPushNotifications =
|
this.usesBroadcastPushNotifications =
|
||||||
|
|||||||
@@ -66,6 +66,11 @@ export async function provideOrGenerateAscApiKeyAsync(
|
|||||||
throw new Error(`A new App Store Connect API Key cannot be created in non-interactive mode.`);
|
throw new Error(`A new App Store Connect API Key cannot be created in non-interactive mode.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When auto-accepting credentials, always auto-generate without asking for user input
|
||||||
|
if (ctx.autoAcceptCredentialReuse) {
|
||||||
|
return await generateAscApiKeyAsync(ctx, purpose);
|
||||||
|
}
|
||||||
|
|
||||||
const userProvided = await promptForAscApiKeyAsync(ctx);
|
const userProvided = await promptForAscApiKeyAsync(ctx);
|
||||||
if (!userProvided) {
|
if (!userProvided) {
|
||||||
return await generateAscApiKeyAsync(ctx, purpose);
|
return await generateAscApiKeyAsync(ctx, purpose);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { pushKeySchema } from '../credentials';
|
|||||||
import { isPushKeyValidAndTrackedAsync } from '../validators/validatePushKey';
|
import { isPushKeyValidAndTrackedAsync } from '../validators/validatePushKey';
|
||||||
|
|
||||||
export async function provideOrGeneratePushKeyAsync(ctx: CredentialsContext): Promise<PushKey> {
|
export async function provideOrGeneratePushKeyAsync(ctx: CredentialsContext): Promise<PushKey> {
|
||||||
if (!ctx.nonInteractive) {
|
if (!ctx.nonInteractive && !ctx.autoAcceptCredentialReuse) {
|
||||||
const userProvided = await promptForPushKeyAsync(ctx);
|
const userProvided = await promptForPushKeyAsync(ctx);
|
||||||
if (userProvided) {
|
if (userProvided) {
|
||||||
if (!ctx.appStore.authCtx) {
|
if (!ctx.appStore.authCtx) {
|
||||||
|
|||||||
@@ -65,6 +65,12 @@ export class SetUpAscApiKey {
|
|||||||
return await new AssignAscApiKey(this.app).runAsync(ctx, maybeAutoselectedKey, this.purpose);
|
return await new AssignAscApiKey(this.app).runAsync(ctx, maybeAutoselectedKey, this.purpose);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When auto-accepting credentials and no valid key was found, generate a new one without prompting
|
||||||
|
if (ctx.autoAcceptCredentialReuse) {
|
||||||
|
const ascApiKey = await new CreateAscApiKey(this.app.account).runAsync(ctx, this.purpose);
|
||||||
|
return await new AssignAscApiKey(this.app).runAsync(ctx, ascApiKey, this.purpose);
|
||||||
|
}
|
||||||
|
|
||||||
const availableChoices =
|
const availableChoices =
|
||||||
keysForAccount.length === 0
|
keysForAccount.length === 0
|
||||||
? this.choices.filter(choice => choice.value !== SetupAscApiKeyChoice.USE_EXISTING)
|
? this.choices.filter(choice => choice.value !== SetupAscApiKeyChoice.USE_EXISTING)
|
||||||
@@ -90,9 +96,11 @@ export class SetUpAscApiKey {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const [autoselectedKey] = sortAscApiKeysByUpdatedAtDesc(validKeys);
|
const [autoselectedKey] = sortAscApiKeysByUpdatedAtDesc(validKeys);
|
||||||
const useAutoselected = await confirmAsync({
|
const useAutoselected =
|
||||||
message: `Reuse this App Store Connect API Key?\n${formatAscApiKey(autoselectedKey)}`,
|
ctx.autoAcceptCredentialReuse ||
|
||||||
});
|
(await confirmAsync({
|
||||||
|
message: `Reuse this App Store Connect API Key?\n${formatAscApiKey(autoselectedKey)}`,
|
||||||
|
}));
|
||||||
|
|
||||||
if (useAutoselected) {
|
if (useAutoselected) {
|
||||||
Log.log(`Using App Store Connect API Key with ID ${autoselectedKey.keyIdentifier}`);
|
Log.log(`Using App Store Connect API Key with ID ${autoselectedKey.keyIdentifier}`);
|
||||||
|
|||||||
@@ -117,11 +117,13 @@ export class SetUpDistributionCertificate {
|
|||||||
const validDistCerts = await this.getValidDistCertsAsync(ctx);
|
const validDistCerts = await this.getValidDistCertsAsync(ctx);
|
||||||
const autoselectedDistCert = validDistCerts[0];
|
const autoselectedDistCert = validDistCerts[0];
|
||||||
|
|
||||||
const useAutoselected = await confirmAsync({
|
const useAutoselected =
|
||||||
message: `Reuse this distribution certificate?\n${formatDistributionCertificate(
|
ctx.autoAcceptCredentialReuse ||
|
||||||
autoselectedDistCert
|
(await confirmAsync({
|
||||||
)}`,
|
message: `Reuse this distribution certificate?\n${formatDistributionCertificate(
|
||||||
});
|
autoselectedDistCert
|
||||||
|
)}`,
|
||||||
|
}));
|
||||||
|
|
||||||
if (useAutoselected) {
|
if (useAutoselected) {
|
||||||
Log.log(
|
Log.log(
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export class SetUpProvisioningProfile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isNonInteractiveOrUserDidConfirmAsync = async (): Promise<boolean> => {
|
const isNonInteractiveOrUserDidConfirmAsync = async (): Promise<boolean> => {
|
||||||
if (ctx.nonInteractive) {
|
if (ctx.nonInteractive || ctx.autoAcceptCredentialReuse) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return await confirmAsync({
|
return await confirmAsync({
|
||||||
|
|||||||
@@ -63,12 +63,14 @@ export class SetUpPushKey {
|
|||||||
if (validPushKeys.length > 0) {
|
if (validPushKeys.length > 0) {
|
||||||
const autoselectedPushKey = validPushKeys[0];
|
const autoselectedPushKey = validPushKeys[0];
|
||||||
|
|
||||||
const useAutoselected = await confirmAsync({
|
const useAutoselected =
|
||||||
message: `Reuse this Push Key?\n${formatPushKey(
|
ctx.autoAcceptCredentialReuse ||
|
||||||
autoselectedPushKey,
|
(await confirmAsync({
|
||||||
validPushKeys.map(pushKey => pushKey.keyIdentifier)
|
message: `Reuse this Push Key?\n${formatPushKey(
|
||||||
)}`,
|
autoselectedPushKey,
|
||||||
});
|
validPushKeys.map(pushKey => pushKey.keyIdentifier)
|
||||||
|
)}`,
|
||||||
|
}));
|
||||||
|
|
||||||
if (useAutoselected) {
|
if (useAutoselected) {
|
||||||
Log.log(`Using push key with ID ${autoselectedPushKey.keyIdentifier}`);
|
Log.log(`Using push key with ID ${autoselectedPushKey.keyIdentifier}`);
|
||||||
@@ -77,6 +79,11 @@ export class SetUpPushKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When auto-accepting and no valid push key was found, generate a new one without prompting
|
||||||
|
if (ctx.autoAcceptCredentialReuse) {
|
||||||
|
return await new CreatePushKey(this.app.account).runAsync(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
const { action } = await promptAsync({
|
const { action } = await promptAsync({
|
||||||
type: 'select',
|
type: 'select',
|
||||||
name: 'action',
|
name: 'action',
|
||||||
|
|||||||
Loaded 100 of 187 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user