Compare commits

..
Author SHA1 Message Date
evanbacon e9088dbb93 [eas-cli] Add changelog entry for non-interactive git fix 2026-02-07 12:56:02 -08:00
evanbacon f08392888d Update git.ts 2026-02-07 11:34:28 -08:00
evanbacon fbcc04eb62 Support nonInteractive in vcs ensureRepoExists
Add an optional nonInteractive parameter to Client.ensureRepoExistsAsync and implement it in GitClient. When nonInteractive is true, GitClient will auto-init the repo (git init), create an initial commit, and surface a single error with suggested git config commands if user.name/email are missing. Update callers across build/update/inspect/configure commands to pass the appropriate nonInteractive flag and adjust ensureRepoIsCleanAsync calls. Also resolve the workspace root for init operations and improve related log messages.
2026-02-07 11:32:51 -08:00
Byron Karlen 42940b7c4c [eas-cli] Add --browser option to eas login (#3312) 2026-02-06 09:51:37 -08:00
Stanisław Chmiela d29a88eafd [chore] Move all CI to Mise (#3379)
<!-- If this PR requires a changelog entry, add it by commenting the PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`. -->
<!-- You can skip the changelog check by labeling the PR with "no changelog". -->

# Why

I noticed I didn't migrate all workflows to Mise (because not all worklfows were using Volta lol).

# How

Codex added a shared Mise action (in other repos we have it set up like this too) and used it in all places where we require Node related to code (there's one workflow which does not use checkout, it remains on `setup-node` too). `test.yml` where we test against different Node versions still uses `setup-node`.

Note: this does downgrade Node in a few workflows from 22 to 20, but if 20 is what we decide to use for our development maybe CI should use it too?

# Test Plan

CI should pass.
2026-02-06 11:58:10 +01:00
Stanisław Chmiela 67087e9471 [chore] Fix pretarball-ci script (#3381)
<!-- If this PR requires a changelog entry, add it by commenting the PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`. -->
<!-- You can skip the changelog check by labeling the PR with "no changelog". -->

# Why

When oclif is building tarballs with executables it does so in a directory separate from the monorepo which requires us to change `eas-cli` dependencies from the monorepo to `file`.

# How

Asked Gemini to update the script to include all local dependencies of `eas-cli`.

Or… we can land https://github.com/expo/eas-cli/pull/3361 …

# Test Plan

Release should pass. Validated locally. (Hmm but on CI it failed? Will see.)

Ok, so size failed because it ran on `main` which is broken because it lacks this pull request.

<img width="944" height="1031" alt="Zrzut ekranu 2026-02-5 o 19 05 41" src="https://github.com/user-attachments/assets/1134493c-bbdb-4f08-85d6-07e59fa28750" />
2026-02-06 11:57:57 +01:00
Stanisław Chmiela 96dc437217 [build-tools] Move uploading IPAs to build-tools (#3383)
<!-- If this PR requires a changelog entry, add it by commenting the PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`. -->
<!-- You can skip the changelog check by labeling the PR with "no changelog". -->

# Why

I want to iterate on this faster.

# How

Asked Codex to move this to build functions.

# Test Plan

Unit tests were moved too. Land and test on staging.
2026-02-05 21:29:52 +01:00
Expo CI f68cbb48d7 v17.0.0 2026-02-05 14:58:59 +00:00
Stanisław Chmiela 50f1269a9c [eas-cli] Some polish to submit:upload-to-asc command (#3362)
# How

I like these flag names a bit better (AI preferred them too). Also made it so we don't wait for build processing by default.

# Test Plan

Tested manually.
2026-02-05 14:23:30 +01:00
Karol Rzemiński 3bc8a8e3e1 [chore] Upgrade tar to v7 (#3327)
# Why

After fixing [GHSA-8qq5-rm4j-mr97](https://github.com/advisories/GHSA-8qq5-rm4j-mr97) by setting resolution of `tar` to 7.5.3 EAS build started failing
<img width="776" height="144" alt="Zrzut ekranu 2026-01-20 o 20 17 57" src="https://github.com/user-attachments/assets/02f8d82e-6316-43c4-95e2-6434599456fd" />
Link to github issue: https://github.com/expo/eas-cli/issues/3320

# How

`tar` 7.x changed from default exports to named exports for better tree-shaking and ESM/CommonJS hybrid support
EAS CLI code still used the old import pattern which no longer exists

**Solution:**
Updated the TypeScript sources to import tar as a namespace instead of a default export
No functional changes besides fixing the broken tar calls, all call sites still use the same options and behavior as before

Additionally, updated `tar` from version 7.5.3 to 7.5.4 to address CVE-2026-23950

Closes https://github.com/expo/eas-cli/pull/3353, https://github.com/expo/eas-cli/pull/3360, https://github.com/expo/eas-cli/pull/3354.

# Test Plan

Run EAS build with tar 7.5.3 to reproduce
After this changes EAS build uploads tarball successfully
2026-02-05 13:53:32 +01:00
Stanisław Chmiela 510de5de4f [build-tools] Poll lockscreen state and retry unlock until it clears (#3377)
# Why

The Cuttlefish device may remain locked after an initial `KEYCODE_MENU` event which blocks reaching the home screen, so the unlock flow must verify the real lock state instead of blindly sending HOME. See https://exponent-internal.slack.com/archives/C02VC9BLMQW/p1769703880840729?thread_ts=1763747642.435979&cid=C02VC9BLMQW.

# How
- Updated `packages/build-tools/src/steps/functions/startCuttlefishDevice.ts` to poll `adb shell dumpsys window` and check `mDreamingLockscreen` before deciding the device is unlocked.
- Implemented a retry loop that sends `adb shell input keyevent 82` while the lockscreen remains active, waits 1s between attempts, and uses a 30 second deadline for the unlock attempts.
- When unlock is detected the code now sends `adb shell input keyevent 3` to return to the home screen and logs success, and it logs warnings on query failures or on timeout.

# Test Plan

- Ran `yarn typecheck` which completed successfully.
- Ran `yarn lint --fix` which completed successfully.

------
[Codex Task](https://chatgpt.com/codex/tasks/task_e_698374b75b88832890754e697e58e5ea)
2026-02-05 13:20:01 +01:00
Stanisław Chmiela 0deeb469d6 [chore] Use Mise for Yarn too (#3378)
<!-- If this PR requires a changelog entry, add it by commenting the PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`. -->
<!-- You can skip the changelog check by labeling the PR with "no changelog". -->

# Why

I knew https://github.com/expo/eas-cli/pull/3366 won't work! ([Failure](https://github.com/expo/eas-cli/actions/runs/21709140672/job/62608616635).)

Using corepack for Yarn breaks EAS Build because when we run `pnpm --version` when setting up the environment, it runs in default cwd which is the worker directory and if worker has `packageManager` different than PNPM, PNPM throws an error. This was originally found in February 2025 by @szdziedzic.

# How

Use Mise for Yarn installation (instead of corepack).

# Test Plan

Merge and see if system tests work.
2026-02-05 13:08:24 +01:00
Stanisław Chmiela 9b91bc37c9 [chore] Migrate from Volta to Mise (#3366)
<!-- If this PR requires a changelog entry, add it by commenting the PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`. -->
<!-- You can skip the changelog check by labeling the PR with "no changelog". -->

# Why

I think we migrated from Volta to Mise in… most if not all places?

# How

I think I let Codex do all the work, nothing too difficult. Grepped repo for "volta" leftover afterwards.

One thing I'm not sure of is whether worker is going to work. I remember trying to remove Volta in `turtle-v2` and it wasn't working for some reason? We'll see.

# Test Plan

Compared these changes with https://github.com/expo/universe/commit/acca8169746c5cb4d708add083f77b927341ae4a.
2026-02-05 12:08:12 +01:00
Stanisław Chmiela 952b275c90 [chore] Revamp testing (#3375)
<!-- If this PR requires a changelog entry, add it by commenting the PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`. -->
<!-- You can skip the changelog check by labeling the PR with "no changelog". -->

# Why

We moved more packages into this monorepo and the existing test setup didn’t scale well:
- the root Jest config only covered a subset of packages,
- PRs ran heavier “all tests” workflows,
- worker builds ran on PRs even though typecheck + tests were enough.

This PR aligns testing with the dependency graph, reduces unnecessary CI work, and standardizes Jest config handling.

# How

- Switched root testing to Lerna and updated docs.
- CI now runs affected+dependent tests on PRs and all packages on main.
- Coverage runs via Lerna with per-package reports (no merge) and is kept for PRs.
- Worker builds are skipped on PRs; worker workflow triggers include dependency paths on main.
- All Jest configs are now TypeScript with a shared base (`jest/jest.shared.config.ts`) and use `@jest/types` to avoid TS config parsing errors.
- CI stays unit-only (no integration tests).

# Test Plan

Not run locally. CI covers:
- `yarn lerna run test` (affected on PRs, all on main)
- `yarn lerna run test -- --coverage`
2026-02-04 21:34:56 +01:00
Colin MacKenzieandClaude Opus 4.5 9dd75445c3 Don't show build credits warning bar at 100% usage (#3371)
* [eas-cli] Don't show build credits warning bar at 100% usage

The warning bar is meant to alert users when they're approaching their
limit. At 100%, they've already hit it, so the bar is redundant.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* Update CHANGELOG for #3371

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* [eas-cli] Hide progress bar (not warning) at 100% usage

The warning should still show at 100% to inform users they've hit the
limit, but the progress bar is redundant when completely full.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>
2026-02-03 15:15:17 -06:00
Stanisław Chmiela da6703706a [build-tools] Add repack_package (#3367)
https://github.com/expo/universe/pull/24634

# Test plan

No values defined

<img width="460" height="199" alt="Zrzut ekranu 2026-02-3 o 15 22 43" src="https://github.com/user-attachments/assets/4a45d554-af83-476e-a221-68dc29f84290" />


With overrides

<img width="694" height="356" alt="Zrzut ekranu 2026-02-3 o 15 11 38" src="https://github.com/user-attachments/assets/2ca1fa76-612f-4a3d-896e-67119fb1f676" />
2026-02-03 20:35:08 +01:00
Evan BaconandClaude Opus 4.5 673d8c3712 List available options in non-interactive mode errors (#3359)
* Improve error message for Apple team selection in non-interactive mode

Enhanced the error thrown when selecting an Apple team in non-interactive mode to include a list of available teams and guidance on using the --apple-team-id flag. This provides better context and instructions for users running the CLI in non-interactive environments.

* List available options in non-interactive mode errors across all selection prompts

Non-interactive mode errors now include the available options so agents and
scripts can discover valid values and retry with the correct flag. This applies
to selection prompts for Apple teams, Apple devices, builds, branches, channels,
update groups, runtimes, rollout actions, and environment variable type/visibility/environment.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* Fix test expectation and update changelog PR number

Update test for selectUpdateGroupOnBranchAsync to match the new error
message when no update groups are found in non-interactive mode. Update
changelog entry with PR #3359.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* Fix prettier formatting

Co-Authored-By: Claude Opus 4.5 <[email protected]>

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>
2026-02-03 10:46:30 -08:00
Stanisław Chmiela c6463af3b9 [worker] Copy bin directory too (#3370)
<!-- If this PR requires a changelog entry, add it by commenting the PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`. -->
<!-- You can skip the changelog check by labeling the PR with "no changelog". -->

# Why

https://staging.expo.dev/accounts/expo-services/projects/worker-system-tests/builds/59628168-4fe6-4cef-981a-fb74527c47ed

# How

Added copying of missing `bin`.

# Test Plan

Merge and observe system tests.
2026-02-03 19:42:50 +01:00
Stanisław Chmiela f94dc6bd59 [worker] Copy all local packages required by @expo/worker (#3368)
`package.sh` currently does not copy workspace packages into the temporary build folder which makes worker pull packages from NPM.

Instead, we want to use local versions from workspace.

Added `copy_package` calls for all dependencies of `@expo/worker`. 

It was done in a similar way in `turtle-v2` — https://github.com/expo/turtle-v2/blob/245eaf23492f2dcd9204a4606bb95c109208d29e/src/services/worker/package.sh#L46-L47.

Ran `./package.sh` and confirmed `@expo/eas-build-job` and other packages are symlinked.
2026-02-03 17:06:53 +01:00
100 changed files with 1278 additions and 1044 deletions

No files matched your search

+11
View File
@@ -0,0 +1,11 @@
name: setup-mise
description: Install tools via mise
runs:
using: "composite"
steps:
- uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3.5.1
with:
install: true
cache: true
experimental: true
@@ -25,7 +25,7 @@ jobs:
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: volta-cli/action@5c175f92dea6f48441c436471e6479dbc192e194 # v4
- uses: ./.github/actions/setup-mise
- uses: ./.github/internal-actions/setup-gcloud
+5 -17
View File
@@ -30,9 +30,7 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- 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
@@ -77,9 +75,7 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: ./.github/actions/setup-mise
- run: brew install python-setuptools
env:
HOMEBREW_NO_AUTO_UPDATE: 1
@@ -122,9 +118,7 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: ./.github/actions/setup-mise
- run: yarn install --frozen-lockfile --check-files
- run: sudo apt-get install nsis
- run: yarn build
@@ -164,11 +158,7 @@ jobs:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
registry-url: 'https://registry.npmjs.org/'
scope: 'expo'
node-version: 22
- uses: ./.github/actions/setup-mise
- name: Install dependencies
run: yarn install --frozen-lockfile --check-files
- name: Build
@@ -199,9 +189,7 @@ jobs:
- uses: actions/checkout@v4
with:
token: ${{ secrets.EXPO_BOT_PAT }}
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: ./.github/actions/setup-mise
- name: Install dependencies
working-directory: ./scripts
run: yarn install --frozen-lockfile --check-files
+1 -3
View File
@@ -10,9 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 20
- uses: ./.github/actions/setup-mise
- uses: preactjs/compressed-size-action@v2
with:
build-script: 'build:tarballs:linux'
+2 -4
View File
@@ -18,10 +18,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: 22
- name: Setup tools
uses: ./.github/actions/setup-mise
- run: yarn install --frozen-lockfile
- run: yarn verify-graphql-code
working-directory: packages/eas-cli
+2 -4
View File
@@ -22,9 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: 20
- name: Setup tools
uses: ./.github/actions/setup-mise
- run: yarn install --frozen-lockfile --check-files
- run: yarn typecheck
+17 -3
View File
@@ -31,12 +31,26 @@ jobs:
- run: |
git config --global user.email "[email protected]"
git config --global user.name "Test"
- run: yarn test
if: ${{ !matrix.coverage }}
- name: Fetch base commit for PR diffing
if: ${{ github.event_name == 'pull_request' }}
# Lerna needs the base commit to compute affected packages.
run: git fetch --no-tags --depth=1 origin ${{ github.event.pull_request.base.sha }}
- name: Run tests (affected)
# PRs: only run tests for changed packages + dependents.
if: ${{ !matrix.coverage && github.event_name == 'pull_request' }}
run: yarn lerna run test --since ${{ github.event.pull_request.base.sha }} --include-dependents
env:
NODE_OPTIONS: --max-old-space-size=8192
- run: yarn test --coverage
- name: Run tests (all)
# Pushes to main: run all package tests.
if: ${{ !matrix.coverage && github.event_name == 'push' }}
run: yarn lerna run test
env:
NODE_OPTIONS: --max-old-space-size=8192
- name: Run tests (coverage)
# Coverage on PRs and main; per-package reports are uploaded to Codecov.
if: ${{ matrix.coverage }}
run: yarn lerna run test -- -- --coverage
env:
# Oclif seems to cause OOM errors when testing with coverage enabled.
# Remove this whenever we solved those OOM errors.
+1 -3
View File
@@ -27,9 +27,7 @@ jobs:
ref: main
fetch-depth: 0
token: ${{ secrets.EXPO_BOT_PAT }}
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: ./.github/actions/setup-mise
- run: |
# Lerna will not detect changes correctly without having all tags
git fetch --depth=1 origin +refs/tags/*:refs/tags/*
@@ -1,81 +0,0 @@
name: Open a PR bumping eas-build modules
on: workflow_dispatch
jobs:
open-pr:
name: Open a PR bumping eas-build modules
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
token: ${{ secrets.EXPO_BOT_PAT }}
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: 20
- run: yarn install --frozen-lockfile
- name: Configure git
run: |
git config --global user.email "[email protected]"
git config --global user.name "Expo Bot"
- name: Upgrade eas-build modules
run: |
for DIR in packages/eas-cli packages/eas-json; do
pushd $DIR >/dev/null 2>&1
# packages are ordered according to output of `lerna list --toposort`
# https://github.com/expo/turtle-v2/pull/568
for PACKAGE in "@expo/downloader" "@expo/eas-build-job" "@expo/logger" "@expo/template-file" "@expo/steps" "@expo/turtle-spawn" "@expo/build-tools"; do
if grep -q $PACKAGE package.json; then
echo "[$DIR] yarn --exact add $PACKAGE"
yarn add --exact $PACKAGE
fi
done
popd >/dev/null 2>&1
done
- name: Set a branch name
id: bump-branch-name
run: |
HASH="$(echo $RANDOM | md5sum | head -c 10)"
BRANCH_NAME="@expo-bot/eas-build-bump-$HASH"
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
- name: Create a branch, commit, and push
run: |
BRANCH_NAME="${{ steps.bump-branch-name.outputs.BRANCH_NAME }}"
git checkout -b $BRANCH_NAME
git add .
git commit -m "Upgrade eas-build dependencies"
git push origin $BRANCH_NAME
- name: Open a PR to main
uses: repo-sync/pull-request@v2
id: open-pr
with:
source_branch: '${{ steps.bump-branch-name.outputs.BRANCH_NAME }}'
destination_branch: 'main'
pr_title: 'Upgrade eas-build dependencies'
pr_body: ':arrow_up: Upgrades [`eas-build`](https://github.com/expo/eas-build) dependencies.'
pr_reviewer: 'sjchmiela'
github_token: ${{ secrets.EXPO_BOT_PAT }}
- uses: actions/github-script@v3
with:
github-token: ${{ secrets.EXPO_BOT_PAT }}
script: |
github.issues.createComment({
issue_number: ${{ steps.open-pr.outputs.pr_number }},
owner: context.repo.owner,
repo: context.repo.repo,
body: "/changelog-entry chore Upgrade [`eas-build`](https://github.com/expo/eas-build) dependencies."
})
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: volta-cli/action@5c175f92dea6f48441c436471e6479dbc192e194 # v4
- uses: ./.github/actions/setup-mise
- name: Create test app
run: npx -y create-expo-app@latest ${{ runner.temp }}/test-app --template tabs
+18 -76
View File
@@ -3,88 +3,35 @@ name: worker
on:
push:
branches: [main]
pull_request:
paths:
- "packages/worker/**"
- "packages/build-tools/**"
- "packages/eas-build-job/**"
- "packages/steps/**"
- "packages/logger/**"
- "packages/downloader/**"
- "packages/template-file/**"
- "packages/turtle-spawn/**"
- .github/workflows/worker.yml
- .github/workflows/build-and-deploy-worker.yml
- yarn.lock
concurrency:
group: worker-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: true
jobs:
changes:
runs-on: ubuntu-latest
outputs:
run_worker: ${{ steps.changes.outputs.run_worker }}
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: volta-cli/action@5c175f92dea6f48441c436471e6479dbc192e194 # v4
- run: yarn install --frozen-lockfile --check-files
- id: changes
name: Determine worker dependency changes
shell: bash
run: |
set -euo pipefail
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
BASE_SHA="${{ github.event.pull_request.base.sha }}"
else
BASE_SHA="${{ github.event.before }}"
fi
if git cat-file -e "$BASE_SHA^{commit}" 2>/dev/null; then
CHANGED_FILES="$(git diff --name-only "$BASE_SHA" "${{ github.sha }}")"
else
CHANGED_FILES="$(git diff --name-only --root "${{ github.sha }}")"
fi
ROOT_DIR="$(git rev-parse --show-toplevel)"
DEP_DIRS="$(yarn --silent lerna list --include-dependencies --scope @expo/worker --parsable)"
RELEVANT_PREFIXES=(
"packages/worker/"
".github/workflows/worker.yml"
".github/workflows/build-and-deploy-worker.yml"
)
while IFS= read -r dir; do
[[ -z "$dir" ]] && continue
# Lerna may emit absolute paths; normalize to repo-relative for git diff matching.
if [[ "$dir" == "$ROOT_DIR"* ]]; then
dir="${dir#"$ROOT_DIR"/}"
else
# Some environments emit paths like ./packages/...
dir="${dir#./}"
fi
RELEVANT_PREFIXES+=("${dir}/")
done <<< "$DEP_DIRS"
RUN_WORKER=false
while IFS= read -r file; do
for prefix in "${RELEVANT_PREFIXES[@]}"; do
if [[ "$file" == "$prefix"* || "$file" == "$prefix" ]]; then
RUN_WORKER=true
break 2
fi
done
done <<< "$CHANGED_FILES"
echo "run_worker=$RUN_WORKER" >> "$GITHUB_OUTPUT"
worker-checks:
if: needs.changes.outputs.run_worker == 'true'
needs:
- changes
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: volta-cli/action@5c175f92dea6f48441c436471e6479dbc192e194 # v4
- uses: ./.github/actions/setup-mise
- run: yarn install --frozen-lockfile --check-files
- run: yarn typecheck
- name: Run unit tests
working-directory: packages/worker
run: yarn test:unit
@@ -94,9 +41,7 @@ jobs:
run: yarn test:integration
worker-build:
if: needs.changes.outputs.run_worker == 'true'
needs:
- changes
- worker-checks
uses: ./.github/workflows/build-and-deploy-worker.yml
secrets: inherit
@@ -104,18 +49,16 @@ jobs:
environment: ${{ github.ref == 'refs/heads/main' && 'staging' || '' }}
system-tests-staging:
if: github.ref == 'refs/heads/main' && !failure() && !cancelled() && needs.changes.outputs.run_worker == 'true'
if: github.ref == 'refs/heads/main' && !failure() && !cancelled()
needs:
- changes
- worker-build
- worker-checks
uses: ./.github/workflows/worker-system-tests.yml
secrets: inherit
deploy-worker-production:
if: github.ref == 'refs/heads/main' && !failure() && !cancelled() && needs.changes.outputs.run_worker == 'true'
if: github.ref == 'refs/heads/main' && !failure() && !cancelled()
needs:
- changes
- system-tests-staging
uses: ./.github/workflows/build-and-deploy-worker.yml
secrets: inherit
@@ -125,9 +68,8 @@ jobs:
notify-slack:
runs-on: ubuntu-latest
needs:
- changes
- deploy-worker-production
if: github.ref == 'refs/heads/main' && always() && !cancelled() && needs.changes.outputs.run_worker == 'true'
if: github.ref == 'refs/heads/main' && always() && !cancelled()
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
+9
View File
@@ -10,14 +10,23 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features
- 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))
### 🐛 Bug fixes
- Fix `--non-interactive` mode prompting for git initialization. Now auto-initializes git with an initial commit when needed. Set `EAS_NO_VCS=1` to skip. ([#3387](https://github.com/expo/eas-cli/pull/3387) by [@evanbacon](https://github.com/evanbacon))
### 🧹 Chores
- Upgrade `tar` to v7. ([#3327](https://github.com/expo/eas-cli/pull/3327) by [@KarolRzeminski](https://github.com/KarolRzeminski))
## [16.32.0](https://github.com/expo/eas-cli/releases/tag/v16.32.0) - 2026-01-30
### 🎉 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))
- 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 App Clip bundle identifier registration support for multi-target iOS builds. ([#3300](https://github.com/expo/eas-cli/pull/3300) by [@evanbacon](https://github.com/evanbacon))
+12 -9
View File
@@ -106,14 +106,17 @@ yarn start-allow-unused
### Testing
```bash
yarn test # Run all tests across packages (Jest multi-project)
yarn test --watch # Run tests in watch mode
yarn lerna run test # Run all tests across packages
yarn lerna run test -- --watch # Watch mode across packages (heavier)
#
# If you use `yarn test`, remember to pass args through two separators:
# yarn test -- -- --watch
# Run tests for specific package
# Run tests for a specific package
cd packages/eas-cli && yarn test
cd packages/eas-json && yarn test
cd packages/worker && yarn test
cd packages/build-tools && yarn jest-unit
cd packages/build-tools && yarn test
cd packages/steps && yarn test
```
@@ -243,8 +246,8 @@ Most packages depend on `@expo/eas-build-job` as the source of truth for types.
## Testing Architecture
- **Framework**: Jest with multi-project configuration
- **Root config**: `jest.config.ts` (combines eas-cli, eas-json, worker)
- **Framework**: Jest with per-package configuration
- **Shared base config**: `jest/jest.shared.config.ts`
- **Test locations**: `__tests__/` directories alongside source files
- **Mocking**:
- File system: `memfs`
@@ -254,9 +257,9 @@ Most packages depend on `@expo/eas-build-job` as the source of truth for types.
Run a single test file:
```bash
yarn test <path-to-test-file>
cd packages/eas-cli && yarn test <path-to-test-file>
# Example:
yarn test packages/eas-cli/src/project/__tests__/projectUtils-test.ts
cd packages/eas-cli && yarn test src/project/__tests__/projectUtils-test.ts
```
## Common Development Scenarios
@@ -351,6 +354,6 @@ See `LICENSE` (MIT) and `LICENSE-BUSL` (BUSL-1.1) for details.
## Important Notes
- **Node Version**: Requires Node.js >= 18.0.0 (managed via Volta)
- **Node Version**: Requires Node.js (managed via Mise)
- **Package Manager**: Uses Yarn 1.22.21
- **Compilation Target**: CommonJS with Node resolution
+1 -1
View File
@@ -69,7 +69,7 @@ See [`CLAUDE.md`](./CLAUDE.md) for how to set up your environment when making ch
## Testing
Run `yarn test` either in the repository root or in a package directory that you're working on.
From the repository root, run `yarn lerna run test`. For a single package, run `yarn test` in that package directory.
## Releasing
-17
View File
@@ -1,17 +0,0 @@
import type { Config } from 'jest';
// eslint-disable-next-line import/no-relative-packages
import easCliConfig from './packages/eas-cli/jest.config';
// eslint-disable-next-line import/no-relative-packages
import easJsonConfig from './packages/eas-json/jest.config';
// eslint-disable-next-line import/no-relative-packages
import workerConfig from './packages/worker/jest.config.unit';
const config: Config = {
projects: [easCliConfig, easJsonConfig, workerConfig],
testPathIgnorePatterns: ['.*'],
watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'],
collectCoverageFrom: ['<rootDir>/src/**/*.{ts,tsx,js,jsx}'],
};
export default config;
-9
View File
@@ -1,9 +0,0 @@
import type { Config } from 'jest';
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
testRegex: '/__tests__/.*(test|spec)\\.[jt]sx?$',
};
export default config;
+8
View File
@@ -0,0 +1,8 @@
import type { Config } from '@jest/types';
const config: Config.InitialOptions = {
preset: 'ts-jest',
testEnvironment: 'node',
};
export default config;
+1 -1
View File
@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"version": "16.32.0",
"version": "17.0.0",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
+3
View File
@@ -0,0 +1,3 @@
[tools]
node = "20.19.4"
yarn = "1.22.22"
+1 -5
View File
@@ -16,7 +16,7 @@
"lint": "eslint .",
"lint-changelog": "./scripts/bin/run lint-changelog",
"release": "lerna run rebuild && ./scripts/bin/run release",
"test": "jest",
"test": "lerna run test",
"clean": "lerna run clean && rm -rf node_modules coverage"
},
"devDependencies": {
@@ -33,9 +33,5 @@
"prettier": "3.1.1",
"ts-jest": "29.1.1",
"typescript": "5.5.4"
},
"volta": {
"node": "20.19.4",
"yarn": "1.22.21"
}
}
@@ -1,8 +1,10 @@
import type { Config } from 'jest';
import type { Config } from '@jest/types';
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../../jest/jest.shared.config';
const config: Config.InitialOptions = {
...baseConfig,
rootDir: '../src',
testMatch: ['**/__integration-tests__/*.test.ts'],
clearMocks: true,
+6 -4
View File
@@ -1,8 +1,10 @@
import type { Config } from 'jest';
import type { Config } from '@jest/types';
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../../jest/jest.shared.config';
const config: Config.InitialOptions = {
...baseConfig,
rootDir: '../src',
testMatch: [
'**/__tests__/*.test.ts',
+10 -12
View File
@@ -5,7 +5,7 @@
"url": "https://github.com/expo/eas-cli.git",
"directory": "packages/build-tools"
},
"version": "1.0.272",
"version": "17.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
@@ -37,17 +37,17 @@
"dependencies": {
"@expo/config": "10.0.6",
"@expo/config-plugins": "9.0.12",
"@expo/downloader": "1.0.260",
"@expo/eas-build-job": "1.0.265",
"@expo/downloader": "17.0.0",
"@expo/eas-build-job": "17.0.0",
"@expo/env": "^0.4.0",
"@expo/logger": "1.0.260",
"@expo/logger": "17.0.0",
"@expo/package-manager": "1.9.10",
"@expo/plist": "^0.2.0",
"@expo/results": "^1.0.0",
"@expo/spawn-async": "1.7.2",
"@expo/steps": "1.0.271",
"@expo/template-file": "1.0.260",
"@expo/turtle-spawn": "1.0.260",
"@expo/steps": "17.0.0",
"@expo/template-file": "17.0.0",
"@expo/turtle-spawn": "17.0.0",
"@expo/xcpretty": "^4.3.1",
"@google-cloud/storage": "^7.11.2",
"@urql/core": "^6.0.1",
@@ -55,6 +55,7 @@
"fs-extra": "^11.2.0",
"gql.tada": "^1.8.13",
"joi": "^17.13.1",
"jose": "5.10.0",
"lodash": "^4.17.21",
"node-fetch": "^2.7.0",
"node-forge": "^1.3.1",
@@ -65,7 +66,7 @@
"resolve-from": "^5.0.0",
"retry": "^0.13.1",
"semver": "^7.6.2",
"tar": "^7.4.3",
"tar": "7.5.7",
"uuid": "9.0.1",
"yaml": "^2.8.1",
"zod": "^4.3.5"
@@ -86,14 +87,11 @@
"bunyan": "^1.8.15",
"jest": "^29.7.0",
"memfs": "^4.17.1",
"nock": "13.4.0",
"ts-jest": "^29.1.4",
"ts-mockito": "^2.6.1",
"tslib": "^2.6.3",
"typescript": "^5.5.4",
"uuid": "^9.0.1"
},
"volta": {
"node": "20.14.0",
"yarn": "1.22.21"
}
}
@@ -36,6 +36,7 @@ import { createStartAndroidEmulatorBuildFunction } from './functions/startAndroi
import { createStartCuttlefishDeviceBuildFunction } from './functions/startCuttlefishDevice';
import { createStartIosSimulatorBuildFunction } from './functions/startIosSimulator';
import { createUploadArtifactBuildFunction } from './functions/uploadArtifact';
import { createUploadToAscBuildFunction } from './functions/uploadToAsc';
import { createSetUpNpmrcBuildFunction } from './functions/useNpmToken';
import { CustomBuildContext } from '../customBuildContext';
@@ -75,6 +76,7 @@ export function getEasFunctions(ctx: CustomBuildContext): BuildFunction[] {
calculateEASUpdateRuntimeVersionFunction(),
createSubmissionEntityFunction(),
createUploadToAscBuildFunction(),
createInternalEasMaestroTestFunction(ctx),
];
@@ -57,6 +57,12 @@ export function createRepackBuildFunction(): BuildFunction {
required: false,
defaultValue: 'latest',
}),
BuildStepInput.createProvider({
id: 'repack_package',
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
required: false,
defaultValue: '@expo/repack-app',
}),
],
outputProviders: [
BuildStepOutput.createProvider({
@@ -93,8 +99,13 @@ export function createRepackBuildFunction(): BuildFunction {
}
: undefined;
stepsCtx.logger.info(`Using repack tool version: ${inputs.repack_version.value}`);
const repackApp = await installAndImportRepackAsync(inputs.repack_version.value as string);
stepsCtx.logger.info(
`Using repack from: ${inputs.repack_package.value}@${inputs.repack_version.value}`
);
const repackApp = await installAndImportRepackAsync({
packageName: inputs.repack_package.value as string,
version: inputs.repack_version.value as string,
});
const { repackAppIosAsync, repackAppAndroidAsync } = repackApp;
stepsCtx.logger.info('Repacking the app...');
@@ -160,15 +171,19 @@ export function createRepackBuildFunction(): BuildFunction {
/**
* Install `@expo/repack-app` in a sandbox directory and import it.
*/
async function installAndImportRepackAsync(
version: string = 'latest'
): Promise<typeof import('@expo/repack-app')> {
async function installAndImportRepackAsync({
packageName,
version,
}: {
packageName: string;
version: string;
}): Promise<typeof import('@expo/repack-app')> {
const sandbox = await fs.promises.mkdtemp(path.join(os.tmpdir(), `repack-package-root-`));
await spawnAsync('yarn', ['add', `@expo/repack-app@${version}`], {
await spawnAsync('yarn', ['add', `${packageName}@${version}`], {
stdio: 'inherit',
cwd: sandbox,
});
return require(resolveFrom(sandbox, '@expo/repack-app'));
return require(resolveFrom(sandbox, packageName));
}
/**
@@ -132,7 +132,34 @@ export function createStartCuttlefishDeviceBuildFunction(): BuildFunction {
logger.info('Listing adb devices...');
await spawn('adb', ['devices'], { env, logger });
await spawn('adb', ['shell', 'input', 'keyevent', '82'], { env, logger });
logger.info('Unlocking device...');
const unlockDeadline = Date.now() + 10_000;
let isUnlocked = false;
while (Date.now() < unlockDeadline) {
const dumpsysResult = await asyncResult(
spawn('adb', ['shell', 'dumpsys', 'window'], { env })
);
if (dumpsysResult.ok) {
const match = dumpsysResult.value.stdout.match(/mDreamingLockscreen=(true|false)/);
if (match?.[1] === 'false') {
isUnlocked = true;
break;
}
} else {
logger.warn(dumpsysResult.reason, 'Failed to query device lock state.');
}
await spawn('adb', ['shell', 'input', 'keyevent', '82'], { env, logger });
await sleepAsync(1_000);
}
if (isUnlocked) {
logger.info('Device unlocked, returning to home screen');
await spawn('adb', ['shell', 'input', 'keyevent', '3'], { env, logger });
} else {
logger.warn('Timed out waiting for the device to unlock.');
}
},
});
}
@@ -0,0 +1,330 @@
import {
BuildFunction,
BuildStepInput,
BuildStepInputValueTypeName,
BuildStepOutput,
} from '@expo/steps';
import fs from 'fs-extra';
import * as jose from 'jose';
import fetch from 'node-fetch';
import path from 'node:path';
import { setTimeout } from 'node:timers/promises';
import { z } from 'zod';
import { AscApiClient, AscApiClientPostApi } from '../utils/ios/AscApiClient';
export function createUploadToAscBuildFunction(): BuildFunction {
return new BuildFunction({
namespace: 'eas',
id: 'upload_to_asc',
name: 'Upload to App Store Connect',
__metricsId: 'eas/upload_to_asc',
inputProviders: [
BuildStepInput.createProvider({
id: 'ipa_path',
required: true,
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
}),
BuildStepInput.createProvider({
id: 'asc_api_key_path',
required: true,
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
}),
BuildStepInput.createProvider({
id: 'apple_app_identifier',
required: true,
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
}),
BuildStepInput.createProvider({
id: 'bundle_version',
required: true,
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
}),
BuildStepInput.createProvider({
id: 'bundle_short_version',
required: true,
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
}),
BuildStepInput.createProvider({
id: 'wait_for_processing',
required: false,
defaultValue: false,
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
}),
],
outputProviders: [
BuildStepOutput.createProvider({
id: 'build_upload_id',
required: false,
}),
BuildStepOutput.createProvider({
id: 'build_upload_url',
required: false,
}),
],
fn: async (stepsCtx, { inputs, outputs }) => {
const ipaPathInput = z.string().parse(inputs.ipa_path.value);
const ascApiKeyPathInput = z.string().parse(inputs.asc_api_key_path.value);
const appleAppIdentifier = z.string().parse(inputs.apple_app_identifier.value);
const bundleVersion = z.string().parse(inputs.bundle_version.value);
const bundleShortVersion = z.string().parse(inputs.bundle_short_version.value);
const waitForProcessing = Boolean(inputs.wait_for_processing.value);
const ipaPath = path.resolve(stepsCtx.workingDirectory, ipaPathInput);
if (!(await fs.pathExists(ipaPath))) {
throw new Error(`IPA file not found: ${ipaPath}`);
}
const fileSize = (await fs.stat(ipaPath)).size;
const fileName = path.basename(ipaPath);
const ascApiKeyPath = path.resolve(stepsCtx.workingDirectory, ascApiKeyPathInput);
if (!(await fs.pathExists(ascApiKeyPath))) {
throw new Error(`ASC API Key file not found: ${ascApiKeyPath}`);
}
const ascApiKeyJson = await fs.readJson(ascApiKeyPath);
const ascApiKey = z
.object({
issuer_id: z.string(),
key_id: z.string(),
key: z.string(),
})
.parse(ascApiKeyJson);
const privateKey = await jose.importPKCS8(ascApiKey.key, 'ES256');
const token = await new jose.SignJWT({})
.setProtectedHeader({ alg: 'ES256', kid: ascApiKey.key_id })
.setIssuer(ascApiKey.issuer_id)
.setAudience('appstoreconnect-v1')
.setExpirationTime('20m')
.sign(privateKey);
const client = new AscApiClient({ token, logger: stepsCtx.logger });
stepsCtx.logger.info('Reading App information...');
const appResponse = await client.getAsync(
'/v1/apps/:id',
{ 'fields[apps]': ['bundleId', 'name'] },
{ id: appleAppIdentifier }
);
stepsCtx.logger.info(
`Uploading Build to "${appResponse.data.attributes.name}" (${appResponse.data.attributes.bundleId})...`
);
stepsCtx.logger.info('Creating Build Upload...');
const buildUploadResponse = await client.postAsync('/v1/buildUploads', {
data: {
type: 'buildUploads',
attributes: {
platform: 'IOS',
cfBundleShortVersionString: bundleShortVersion,
cfBundleVersion: bundleVersion,
},
relationships: {
app: {
data: {
type: 'apps',
id: appleAppIdentifier,
},
},
},
},
});
const buildUploadId = buildUploadResponse.data.id;
const buildUploadUrl = `https://appstoreconnect.apple.com/apps/${appleAppIdentifier}/testflight/ios/${buildUploadId}`;
outputs.build_upload_id.set(buildUploadId);
outputs.build_upload_url.set(buildUploadUrl);
stepsCtx.logger.info(
`Build Upload initialized (ID: ${buildUploadId}). Preparing IPA upload...`
);
const buildFileResponse = await client.postAsync('/v1/buildUploadFiles', {
data: {
type: 'buildUploadFiles',
attributes: {
assetType: 'ASSET',
fileName,
fileSize,
uti: 'com.apple.ipa',
},
relationships: {
buildUpload: {
data: {
type: 'buildUploads',
id: buildUploadId,
},
},
},
},
});
stepsCtx.logger.info(
`IPA Upload initialized (ID: ${buildFileResponse.data.id}). Uploading IPA...`
);
await uploadChunksAsync({
uploadOperations: buildFileResponse.data.attributes.uploadOperations,
ipaPath,
logger: stepsCtx.logger,
});
stepsCtx.logger.info('Committing upload...');
await client.patchAsync(
`/v1/buildUploadFiles/:id`,
{
data: {
type: 'buildUploadFiles',
id: buildFileResponse.data.id,
attributes: {
uploaded: true,
},
},
},
{
id: buildFileResponse.data.id,
}
);
stepsCtx.logger.info('Checking upload file status...');
const waitingForFileStartedAt = Date.now();
while (Date.now() - waitingForFileStartedAt < 60 * 1000 /* 60 seconds */) {
const {
data: {
attributes: { assetDeliveryState },
},
} = await client.getAsync(
`/v1/buildUploadFiles/:id`,
{ 'fields[buildUploadFiles]': ['assetDeliveryState'] },
{ id: buildFileResponse.data.id }
);
if (assetDeliveryState.state === 'AWAITING_UPLOAD') {
stepsCtx.logger.info(
`Waiting for file upload to complete processing... (state = ${assetDeliveryState.state})`
);
await setTimeout(2000);
continue;
}
const { errors = [], warnings = [] } = assetDeliveryState;
if (warnings.length > 0) {
stepsCtx.logger.warn(`Warnings:\n${itemizeMessages(warnings)}\n`);
}
if (errors.length > 0) {
stepsCtx.logger.error(`Errors:\n${itemizeMessages(errors)}\n`);
}
if (assetDeliveryState.state === 'FAILED') {
throw new Error(`File upload (ID: ${buildFileResponse.data.id}) failed.`);
} else if (
assetDeliveryState.state === 'COMPLETE' ||
assetDeliveryState.state === 'UPLOAD_COMPLETE'
) {
stepsCtx.logger.info(`File upload (ID: ${buildFileResponse.data.id}) completed!`);
}
break;
}
stepsCtx.logger.info(`See your build in App Store Connect: ${buildUploadUrl}`);
if (!waitForProcessing) {
stepsCtx.logger.info('Skipping waiting for processing.');
return;
}
stepsCtx.logger.info('Checking build upload status...');
const waitingForBuildStartedAt = Date.now();
while (Date.now() - waitingForBuildStartedAt < 10 * 60 * 1000 /* 10 minutes */) {
const {
data: {
attributes: { state },
},
} = await client.getAsync(
`/v1/buildUploads/:id`,
{ 'fields[buildUploads]': ['state', 'build'], include: ['build'] },
{ id: buildUploadId }
);
if (state.state === 'AWAITING_UPLOAD' || state.state === 'PROCESSING') {
stepsCtx.logger.info(`Waiting for build upload to complete... (status = ${state.state})`);
await setTimeout(2000);
continue;
}
stepsCtx.logger.info('\n');
const { errors = [], warnings = [], infos = [] } = state;
if (infos.length > 0) {
stepsCtx.logger.info(`Infos:\n${itemizeMessages(infos)}\n`);
}
if (warnings.length > 0) {
stepsCtx.logger.warn(`Warnings:\n${itemizeMessages(warnings)}\n`);
}
if (errors.length > 0) {
stepsCtx.logger.error(`Errors:\n${itemizeMessages(errors)}\n`);
}
if (state.state === 'FAILED') {
throw new Error(`Build upload (ID: ${buildUploadId}) failed.`);
} else if (state.state === 'COMPLETE') {
stepsCtx.logger.info(`Build upload (ID: ${buildUploadId}) complete!`);
}
break;
}
},
});
}
function itemizeMessages(messages: { description: string; code: string }[]): string {
return `- ${messages.map(m => `${m.description} (${m.code})`).join('\n- ')}`;
}
async function uploadChunksAsync({
uploadOperations,
ipaPath,
logger,
}: {
uploadOperations: AscApiClientPostApi['/v1/buildUploadFiles']['response']['data']['attributes']['uploadOperations'];
ipaPath: string;
logger: { info: (message: string) => void };
}): Promise<void> {
const fd = await fs.open(ipaPath, 'r');
try {
const promises = uploadOperations.map(async (op, index) => {
logger.info(
` Uploading chunk ${index + 1}/${uploadOperations.length} (offset: ${op.offset}, length: ${
op.length
})...`
);
const buffer = new Uint8Array(op.length);
await fs.read(fd, buffer, 0, op.length, op.offset);
const headers: Record<string, string> = {
'Content-Type': 'application/octet-stream',
};
for (const { name, value } of op.requestHeaders) {
headers[name] = value;
}
const response = await fetch(op.url, {
method: op.method,
headers,
body: buffer,
});
if (!response.ok) {
const text = await response.text();
throw new Error(
`Failed to upload chunk ${index + 1}: ${response.status} ${response.statusText}\n${text}`
);
}
logger.info(` Chunk ${index + 1}/${uploadOperations.length} uploaded.`);
});
await Promise.all(promises);
} finally {
await fs.close(fd);
}
}
@@ -1,9 +1,8 @@
import { bunyan } from '@expo/logger';
import { asyncResult } from '@expo/results';
import fetch from 'node-fetch';
import { ZodError, z } from 'zod';
import Log from '../../log';
type ApiSchema = {
[Path in string]: {
path?: z.ZodType<Record<string, string>>;
@@ -240,9 +239,11 @@ export type AscApiClientPatchApi = {
export class AscApiClient {
private readonly baseUrl = 'https://api.appstoreconnect.apple.com';
private readonly token: string;
private readonly logger?: bunyan;
constructor({ token }: { token: string }) {
constructor({ token, logger }: { token: string; logger?: bunyan }) {
this.token = token;
this.logger = logger;
}
public async getAsync<TPath extends keyof typeof GetApi>(
@@ -257,9 +258,18 @@ export class AscApiClient {
effectivePath = effectivePath.replace(`:${key}`, String(value));
}
const searchParams = new URLSearchParams();
for (const [key, value] of Object.entries(body)) {
if (Array.isArray(value)) {
searchParams.set(key, value.join(','));
} else {
searchParams.set(key, String(value));
}
}
return await this.sendRequestAsync({
method: 'GET',
path: `${effectivePath}?${new URLSearchParams(body).toString()}`,
path: `${effectivePath}?${searchParams.toString()}`,
body,
requestSchema: schema.request,
responseSchema: schema.response,
@@ -343,7 +353,7 @@ export class AscApiClient {
const json = await response.json();
Log.debug(`Response from App Store Connect: ${JSON.stringify(json, null, 2)}`);
this.logger?.debug(`Response from App Store Connect: ${JSON.stringify(json, null, 2)}`);
const parsedResponse = await asyncResult((async () => responseSchema.parse(json))());
if (!parsedResponse.ok) {
@@ -2,6 +2,9 @@ import nock from 'nock';
import { AscApiClient } from '../AscApiClient';
// nock needs real fetch implementation
jest.unmock('node-fetch');
describe(AscApiClient, () => {
const token = 'test-token';
const client = new AscApiClient({ token });
@@ -1,4 +1,10 @@
module.exports = {
import type { Config } from '@jest/types';
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../jest/jest.shared.config';
const config: Config.InitialOptions = {
...baseConfig,
preset: 'ts-jest/presets/default-esm',
transform: {
'^.+\\.tsx?$': [
@@ -9,7 +15,6 @@ module.exports = {
},
],
},
testEnvironment: 'node',
rootDir: 'src',
testMatch: ['**/__tests__/*-test.ts'],
coverageReporters: ['json', 'lcov'],
@@ -21,3 +26,5 @@ module.exports = {
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
export default config;
@@ -5,7 +5,7 @@
"url": "https://github.com/expo/eas-cli.git",
"directory": "packages/create-eas-build-function"
},
"version": "1.0.271",
"version": "17.0.0",
"bin": {
"create-eas-build-function": "./build/index.js"
},
@@ -34,7 +34,7 @@
"clean": "rimraf ./build/ *.tsbuildinfo"
},
"dependencies": {
"@expo/steps": "1.0.271"
"@expo/steps": "17.0.0"
},
"devDependencies": {
"@expo/package-manager": "1.9.10",
@@ -53,9 +53,5 @@
},
"publishConfig": {
"access": "public"
},
"volta": {
"node": "20.14.0",
"yarn": "1.22.21"
}
}
-8
View File
@@ -1,8 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: 'src',
testMatch: ['**/__tests__/*.test.ts'],
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
+14
View File
@@ -0,0 +1,14 @@
import type { Config } from '@jest/types';
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../jest/jest.shared.config';
const config: Config.InitialOptions = {
...baseConfig,
rootDir: 'src',
testMatch: ['**/__tests__/*.test.ts'],
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
export default config;
+3 -7
View File
@@ -5,7 +5,7 @@
"url": "https://github.com/expo/eas-cli.git",
"directory": "packages/downloader"
},
"version": "1.0.260",
"version": "17.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
@@ -17,8 +17,8 @@
"build": "tsc",
"typecheck": "tsc",
"prepack": "rm -rf dist && tsc -p tsconfig.build.json",
"test": "jest --config jest.config.js",
"test:watch": "jest --config jest.config.js --watch",
"test": "jest --config jest.config.ts",
"test:watch": "jest --config jest.config.ts --watch",
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
},
"author": "Expo <[email protected]>",
@@ -35,9 +35,5 @@
"jest": "^29.7.0",
"ts-jest": "^29.1.4",
"typescript": "^5.5.4"
},
"volta": {
"node": "20.14.0",
"yarn": "1.22.21"
}
}
@@ -1,7 +1,7 @@
{
"name": "eas-build-cache-provider",
"description": "A build cache provider plugin for the Expo CLI",
"version": "16.30.0",
"version": "17.0.0",
"author": "Expo <[email protected]>",
"bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": {
@@ -46,7 +46,7 @@
"typecheck": "tsc",
"prepack": "yarn rebuild",
"rebuild": "rimraf build && yarn build",
"test": "jest",
"test": "jest --passWithNoTests",
"clean": "rimraf build node_modules yarn-error.log *.tsbuildinfo"
},
"files": [
-8
View File
@@ -1,8 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: 'src',
testMatch: ['**/__tests__/*.test.ts'],
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
+14
View File
@@ -0,0 +1,14 @@
import type { Config } from '@jest/types';
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../jest/jest.shared.config';
const config: Config.InitialOptions = {
...baseConfig,
rootDir: 'src',
testMatch: ['**/__tests__/*.test.ts'],
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
export default config;
+4 -8
View File
@@ -5,7 +5,7 @@
"url": "https://github.com/expo/eas-cli.git",
"directory": "packages/eas-build-job"
},
"version": "1.0.265",
"version": "17.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
@@ -17,8 +17,8 @@
"build": "tsc",
"typecheck": "tsc",
"prepack": "rm -rf dist && tsc -p tsconfig.build.json",
"test": "jest --config jest.config.js",
"test:watch": "jest --config jest.config.js --watch",
"test": "jest --config jest.config.ts",
"test:watch": "jest --config jest.config.ts --watch",
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
},
"author": "Expo <[email protected]>",
@@ -32,13 +32,9 @@
"typescript": "^5.5.4"
},
"dependencies": {
"@expo/logger": "1.0.260",
"@expo/logger": "17.0.0",
"joi": "^17.13.1",
"semver": "^7.6.2",
"zod": "^4.3.5"
},
"volta": {
"node": "20.14.0",
"yarn": "1.22.21"
}
}
+88 -88
View File
@@ -177,7 +177,7 @@ ALIASES
$ eas login
```
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/account/login.ts)_
## `eas account:logout`
@@ -194,7 +194,7 @@ ALIASES
$ eas logout
```
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/account/logout.ts)_
## `eas account:usage [ACCOUNT_NAME]`
@@ -216,7 +216,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/account/usage.ts)_
## `eas account:view`
@@ -233,7 +233,7 @@ ALIASES
$ eas whoami
```
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/account/view.ts)_
## `eas analytics [STATUS]`
@@ -247,7 +247,7 @@ DESCRIPTION
display or change analytics settings
```
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v16.32.0/packages/eas-cli/src/commands/analytics.ts)_
_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)_
## `eas autocomplete [SHELL]`
@@ -299,7 +299,7 @@ DESCRIPTION
create a branch
```
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/branch/create.ts)_
## `eas branch:delete [NAME]`
@@ -320,7 +320,7 @@ DESCRIPTION
delete a branch
```
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/branch/delete.ts)_
## `eas branch:list`
@@ -340,7 +340,7 @@ DESCRIPTION
list all branches
```
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/branch/list.ts)_
## `eas branch:rename`
@@ -360,7 +360,7 @@ DESCRIPTION
rename a branch
```
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/branch/rename.ts)_
## `eas branch:view [NAME]`
@@ -383,7 +383,7 @@ DESCRIPTION
view a branch
```
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/branch/view.ts)_
## `eas build`
@@ -424,7 +424,7 @@ DESCRIPTION
start a build
```
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/index.ts)_
## `eas build:cancel [BUILD_ID]`
@@ -443,7 +443,7 @@ DESCRIPTION
cancel a build
```
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/cancel.ts)_
## `eas build:configure`
@@ -460,7 +460,7 @@ DESCRIPTION
configure the project to support EAS Build
```
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/configure.ts)_
## `eas build:delete [BUILD_ID]`
@@ -479,7 +479,7 @@ DESCRIPTION
delete a build
```
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/delete.ts)_
## `eas build:dev`
@@ -499,7 +499,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/dev.ts)_
## `eas build:download`
@@ -520,7 +520,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/download.ts)_
## `eas build:inspect`
@@ -555,7 +555,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/inspect.ts)_
## `eas build:list`
@@ -607,7 +607,7 @@ DESCRIPTION
list all builds for your project
```
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/list.ts)_
## `eas build:resign`
@@ -636,7 +636,7 @@ DESCRIPTION
re-sign a build archive
```
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/resign.ts)_
## `eas build:run`
@@ -662,7 +662,7 @@ DESCRIPTION
run simulator/emulator builds from eas-cli
```
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/run.ts)_
## `eas build:submit`
@@ -716,7 +716,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/version/get.ts)_
## `eas build:version:set`
@@ -735,7 +735,7 @@ DESCRIPTION
update version of an app
```
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/version/set.ts)_
## `eas build:version:sync`
@@ -754,7 +754,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/version/sync.ts)_
## `eas build:view [BUILD_ID]`
@@ -771,7 +771,7 @@ DESCRIPTION
view a build for your project
```
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/view.ts)_
## `eas channel:create [NAME]`
@@ -792,7 +792,7 @@ DESCRIPTION
create a channel
```
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/channel/create.ts)_
## `eas channel:delete [NAME]`
@@ -813,7 +813,7 @@ DESCRIPTION
Delete a channel
```
_See code: [packages/eas-cli/src/commands/channel/delete.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/channel/delete.ts)_
## `eas channel:edit [NAME]`
@@ -835,7 +835,7 @@ DESCRIPTION
point a channel at a new branch
```
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/channel/edit.ts)_
## `eas channel:list`
@@ -855,7 +855,7 @@ DESCRIPTION
list all channels
```
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/channel/list.ts)_
## `eas channel:pause [NAME]`
@@ -877,7 +877,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/channel/pause.ts)_
## `eas channel:resume [NAME]`
@@ -899,7 +899,7 @@ DESCRIPTION
resume a channel to start sending updates
```
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/channel/resume.ts)_
## `eas channel:rollout [CHANNEL]`
@@ -932,7 +932,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/channel/rollout.ts)_
## `eas channel:view [NAME]`
@@ -955,7 +955,7 @@ DESCRIPTION
view a channel
```
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/channel/view.ts)_
## `eas config`
@@ -976,7 +976,7 @@ DESCRIPTION
display project configuration (app.json + eas.json)
```
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v16.32.0/packages/eas-cli/src/commands/config.ts)_
_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)_
## `eas credentials`
@@ -993,7 +993,7 @@ DESCRIPTION
manage credentials
```
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/credentials/index.ts)_
## `eas credentials:configure-build`
@@ -1011,7 +1011,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
## `eas deploy [options]`
@@ -1039,7 +1039,7 @@ ALIASES
$ eas worker:deploy
```
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/deploy/index.ts)_
## `eas deploy:alias`
@@ -1064,7 +1064,7 @@ ALIASES
$ eas deploy:promote
```
_See code: [packages/eas-cli/src/commands/deploy/alias/index.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/deploy/alias/index.ts)_
## `eas deploy:alias:delete [ALIAS_NAME]`
@@ -1085,7 +1085,7 @@ ALIASES
$ eas worker:alias:delete
```
_See code: [packages/eas-cli/src/commands/deploy/alias/delete.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/deploy/alias/delete.ts)_
## `eas deploy:delete [DEPLOYMENT_ID]`
@@ -1106,7 +1106,7 @@ ALIASES
$ eas worker:delete
```
_See code: [packages/eas-cli/src/commands/deploy/delete.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/deploy/delete.ts)_
## `eas deploy:promote`
@@ -1143,7 +1143,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/device/create.ts)_
## `eas device:delete`
@@ -1163,7 +1163,7 @@ DESCRIPTION
remove a registered device from your account
```
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/device/delete.ts)_
## `eas device:list`
@@ -1184,7 +1184,7 @@ DESCRIPTION
list all registered devices for your account
```
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/device/list.ts)_
## `eas device:rename`
@@ -1205,7 +1205,7 @@ DESCRIPTION
rename a registered device
```
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/device/rename.ts)_
## `eas device:view [UDID]`
@@ -1219,7 +1219,7 @@ DESCRIPTION
view a device for your project
```
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/device/view.ts)_
## `eas diagnostics`
@@ -1233,7 +1233,7 @@ DESCRIPTION
display environment info
```
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v16.32.0/packages/eas-cli/src/commands/diagnostics.ts)_
_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)_
## `eas env:create [ENVIRONMENT]`
@@ -1263,7 +1263,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/env/create.ts)_
## `eas env:delete [ENVIRONMENT]`
@@ -1288,7 +1288,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/env/delete.ts)_
## `eas env:exec ENVIRONMENT BASH_COMMAND`
@@ -1310,7 +1310,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/env/exec.ts)_
## `eas env:get [ENVIRONMENT]`
@@ -1335,7 +1335,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/env/get.ts)_
## `eas env:list [ENVIRONMENT]`
@@ -1361,7 +1361,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/env/list.ts)_
## `eas env:pull [ENVIRONMENT]`
@@ -1383,7 +1383,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/env/pull.ts)_
## `eas env:push [ENVIRONMENT]`
@@ -1405,7 +1405,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/env/push.ts)_
## `eas env:update [ENVIRONMENT]`
@@ -1437,7 +1437,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/env/update.ts)_
## `eas fingerprint:compare [HASH1] [HASH2]`
@@ -1481,7 +1481,7 @@ EXAMPLES
$ 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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/fingerprint/compare.ts)_
## `eas fingerprint:generate`
@@ -1511,7 +1511,7 @@ EXAMPLES
$ 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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/fingerprint/generate.ts)_
## `eas help [COMMAND]`
@@ -1619,7 +1619,7 @@ DESCRIPTION
validate the local store configuration
```
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/metadata/lint.ts)_
## `eas metadata:pull`
@@ -1636,7 +1636,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/metadata/pull.ts)_
## `eas metadata:push`
@@ -1653,7 +1653,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/metadata/push.ts)_
## `eas new [PATH]`
@@ -1704,7 +1704,7 @@ DESCRIPTION
open the project page in a web browser
```
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v16.32.0/packages/eas-cli/src/commands/open.ts)_
_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)_
## `eas project:info`
@@ -1718,7 +1718,7 @@ DESCRIPTION
information about the current project
```
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/project/info.ts)_
## `eas project:init`
@@ -1741,7 +1741,7 @@ ALIASES
$ eas init
```
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/project/init.ts)_
## `eas project:new [PATH]`
@@ -1764,7 +1764,7 @@ ALIASES
$ eas new
```
_See code: [packages/eas-cli/src/commands/project/new.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/project/new.ts)_
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
@@ -1782,7 +1782,7 @@ ALIASES
$ eas onboarding
```
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/project/onboarding.ts)_
## `eas submit`
@@ -1817,7 +1817,7 @@ ALIASES
$ eas build:submit
```
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v16.32.0/packages/eas-cli/src/commands/submit.ts)_
_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)_
## `eas update`
@@ -1857,7 +1857,7 @@ DESCRIPTION
publish an update group
```
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/index.ts)_
## `eas update:configure`
@@ -1877,7 +1877,7 @@ DESCRIPTION
configure the project to support EAS Update
```
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/configure.ts)_
## `eas update:delete GROUPID`
@@ -1898,7 +1898,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/delete.ts)_
## `eas update:edit [GROUPID]`
@@ -1922,7 +1922,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/edit.ts)_
## `eas update:list`
@@ -1947,7 +1947,7 @@ DESCRIPTION
view the recent updates
```
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/list.ts)_
## `eas update:republish`
@@ -1983,7 +1983,7 @@ DESCRIPTION
roll back to an existing update
```
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/republish.ts)_
## `eas update:revert-update-rollout`
@@ -2010,7 +2010,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/revert-update-rollout.ts)_
## `eas update:roll-back-to-embedded`
@@ -2038,7 +2038,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
## `eas update:rollback`
@@ -2059,7 +2059,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/rollback.ts)_
## `eas update:view GROUPID`
@@ -2079,7 +2079,7 @@ DESCRIPTION
update group details
```
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/view.ts)_
## `eas upload`
@@ -2100,7 +2100,7 @@ DESCRIPTION
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/v16.32.0/packages/eas-cli/src/commands/upload.ts)_
_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)_
## `eas webhook:create`
@@ -2121,7 +2121,7 @@ DESCRIPTION
create a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/webhook/create.ts)_
## `eas webhook:delete [ID]`
@@ -2141,7 +2141,7 @@ DESCRIPTION
delete a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/webhook/delete.ts)_
## `eas webhook:list`
@@ -2159,7 +2159,7 @@ DESCRIPTION
list webhooks
```
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/webhook/list.ts)_
## `eas webhook:update`
@@ -2181,7 +2181,7 @@ DESCRIPTION
update a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/webhook/update.ts)_
## `eas webhook:view ID`
@@ -2198,7 +2198,7 @@ DESCRIPTION
view a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/webhook/view.ts)_
## `eas whoami`
@@ -2318,7 +2318,7 @@ DESCRIPTION
to cancel.
```
_See code: [packages/eas-cli/src/commands/workflow/cancel.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/workflow/cancel.ts)_
## `eas workflow:create [NAME]`
@@ -2338,7 +2338,7 @@ DESCRIPTION
create a new workflow configuration YAML file
```
_See code: [packages/eas-cli/src/commands/workflow/create.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/workflow/create.ts)_
## `eas workflow:logs [ID]`
@@ -2362,7 +2362,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/workflow/logs.ts)_
## `eas workflow:run [FILE]`
@@ -2402,7 +2402,7 @@ FLAG DESCRIPTIONS
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/workflow/run.ts)_
## `eas workflow:runs`
@@ -2427,7 +2427,7 @@ DESCRIPTION
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/workflow/runs.ts)_
## `eas workflow:status [WORKFLOW_RUN_ID]`
@@ -2455,7 +2455,7 @@ FLAG DESCRIPTIONS
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/v16.32.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/v17.0.0/packages/eas-cli/src/commands/workflow/status.ts)_
## `eas workflow:validate PATH`
@@ -2475,7 +2475,7 @@ DESCRIPTION
validate a workflow configuration yaml file
```
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/workflow/validate.ts)_
## `eas workflow:view [ID]`
@@ -2497,5 +2497,5 @@ DESCRIPTION
workflow runs for the current project.
```
_See code: [packages/eas-cli/src/commands/workflow/view.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/workflow/view.ts)_
<!-- commandsstop -->
+4 -3
View File
@@ -1,12 +1,13 @@
import type { Config } from 'jest';
import type { Config } from '@jest/types';
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../jest/jest.config';
import baseConfig from '../../jest/jest.shared.config';
const config: Config = {
const config: Config.InitialOptions = {
...baseConfig,
rootDir: __dirname,
roots: ['src', '__mocks__'],
testRegex: '/__tests__/.*(test|spec)\\.[jt]sx?$',
globalSetup: './jest/global-setup.ts',
setupFilesAfterEnv: ['./jest/setup-after-env.ts'],
};
+6 -8
View File
@@ -1,7 +1,7 @@
{
"name": "eas-cli",
"description": "EAS command line tool",
"version": "16.32.0",
"version": "17.0.0",
"author": "Expo <[email protected]>",
"bin": {
"eas": "./bin/run"
@@ -12,11 +12,11 @@
"@expo/code-signing-certificates": "0.0.5",
"@expo/config": "10.0.6",
"@expo/config-plugins": "9.0.12",
"@expo/eas-build-job": "1.0.265",
"@expo/eas-json": "16.30.0",
"@expo/eas-build-job": "17.0.0",
"@expo/eas-json": "17.0.0",
"@expo/env": "^1.0.0",
"@expo/json-file": "8.3.3",
"@expo/logger": "1.0.260",
"@expo/logger": "17.0.0",
"@expo/multipart-body-parser": "2.0.0",
"@expo/osascript": "2.1.4",
"@expo/package-manager": "1.9.10",
@@ -28,7 +28,7 @@
"@expo/results": "1.0.0",
"@expo/rudder-sdk-node": "1.1.1",
"@expo/spawn-async": "1.7.2",
"@expo/steps": "1.0.271",
"@expo/steps": "17.0.0",
"@expo/timeago.js": "1.0.0",
"@oclif/core": "^1.26.2",
"@oclif/plugin-autocomplete": "^2.3.10",
@@ -61,7 +61,6 @@
"invariant": "^2.2.2",
"jks-js": "1.1.0",
"joi": "17.11.0",
"jsonwebtoken": "9.0.0",
"keychain": "1.5.0",
"log-symbols": "4.1.0",
"mime": "3.0.0",
@@ -83,7 +82,7 @@
"semver": "7.5.4",
"set-interval-async": "3.0.3",
"slash": "3.0.0",
"tar": "6.2.1",
"tar": "7.5.7",
"tar-stream": "3.1.7",
"terminal-link": "2.1.1",
"ts-deepmerge": "6.2.0",
@@ -109,7 +108,6 @@
"@types/fs-extra": "11.0.4",
"@types/getenv": "^1.0.0",
"@types/invariant": "^2.2.37",
"@types/jsonwebtoken": "8.5.1",
"@types/mime": "3.0.1",
"@types/node-fetch": "2.6.12",
"@types/node-forge": "1.3.1",
+54 -5
View File
@@ -5,8 +5,57 @@ set -eo pipefail
EAS_CLI_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
PACKAGE_JSON_PATH="$EAS_CLI_DIR/package.json"
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "s/\\\"@expo\/eas-json\\\": \\\".*\\\"/\\\"@expo\/eas-json\\\": \\\"file:..\/..\/..\/eas-json\\\"/g" $PACKAGE_JSON_PATH
else
sed -i "s/\\\"@expo\/eas-json\\\": \\\".*\\\"/\\\"@expo\/eas-json\\\": \\\"file:..\/..\/..\/eas-json\\\"/g" $PACKAGE_JSON_PATH
fi
# 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
+13 -1
View File
@@ -40,7 +40,19 @@ export async function selectBranchOnAppAsync(
}
): Promise<UpdateBranchFragment> {
if (paginatedQueryOptions.nonInteractive) {
throw new Error('Unable to select a branch in non-interactive mode.');
const branches = await queryBranchesOnProjectAsync(
graphqlClient,
paginatedQueryOptions.limit ?? BRANCHES_LIMIT,
paginatedQueryOptions.offset,
projectId
);
if (branches.length === 0) {
throw new Error(`No branches found for project "${projectId}".`);
}
const branchList = branches.map(b => b.name).join('\n ');
throw new Error(
`Unable to select a branch in non-interactive mode. Use the --branch flag to specify the branch. Available branches:\n ${branchList}`
);
}
const selectedBranch = await paginatedQueryWithSelectPromptAsync({
+20 -1
View File
@@ -77,7 +77,26 @@ export async function listAndSelectBuildOnAppAsync(
}
): Promise<BuildFragment | null> {
if (paginatedQueryOptions.nonInteractive) {
throw new Error('Unable to select a build in non-interactive mode.');
const builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
appId: projectId,
limit: paginatedQueryOptions.limit ?? BUILDS_LIMIT,
offset: paginatedQueryOptions.offset,
filter,
});
if (builds.length === 0) {
throw new Error('No builds found for this project.');
}
const buildList = builds
.map(
b =>
`${b.id} (platform: ${b.platform}, status: ${b.status}${
b.buildProfile ? `, profile: ${b.buildProfile}` : ''
})`
)
.join('\n ');
throw new Error(
`Unable to select a build in non-interactive mode. Available builds:\n ${buildList}`
);
} else {
const selectedBuild = await paginatedQueryWithSelectPromptAsync({
limit: paginatedQueryOptions.limit ?? BUILDS_LIMIT,
@@ -128,7 +128,7 @@ export async function runBuildAndSubmitAsync({
buildIds: string[];
buildProfiles?: ProfileData<'build'>[];
}> {
await vcsClient.ensureRepoExistsAsync();
await vcsClient.ensureRepoExistsAsync({ nonInteractive: flags.nonInteractive });
await ensureRepoIsCleanAsync(vcsClient, flags.nonInteractive);
await ensureProjectConfiguredAsync({
@@ -1,7 +1,7 @@
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
import tar from 'tar';
import * as tar from 'tar';
import { v4 as uuidv4 } from 'uuid';
import Log, { learnMore } from '../../log';
+12 -1
View File
@@ -43,7 +43,18 @@ export async function selectChannelOnAppAsync(
}
): Promise<UpdateChannelObject> {
if (paginatedQueryOptions.nonInteractive) {
throw new Error('Unable to select a channel in non-interactive mode.');
const channels = await queryChannelsOnAppAsync(graphqlClient, {
appId: projectId,
limit: paginatedQueryOptions.limit ?? CHANNELS_LIMIT,
offset: paginatedQueryOptions.offset,
});
if (channels.length === 0) {
throw new Error(`No channels found for project "${projectId}".`);
}
const channelList = channels.map(c => c.name).join('\n ');
throw new Error(
`Unable to select a channel in non-interactive mode. Use the --channel flag to specify the channel. Available channels:\n ${channelList}`
);
}
const updateChannel = await paginatedQueryWithSelectPromptAsync({
@@ -17,6 +17,11 @@ export default class AccountLogin extends EasCommand {
char: 's',
default: false,
}),
browser: Flags.boolean({
description: 'Login with your browser',
char: 'b',
default: false,
}),
};
static override contextDefinition = {
@@ -26,7 +31,7 @@ export default class AccountLogin extends EasCommand {
async runAsync(): Promise<void> {
const {
flags: { sso },
flags: { sso, browser },
} = await this.parse(AccountLogin);
const {
@@ -51,7 +56,7 @@ export default class AccountLogin extends EasCommand {
}
}
await sessionManager.showLoginPromptAsync({ sso });
await sessionManager.showLoginPromptAsync({ sso, browser });
Log.log('Logged in');
}
}
@@ -46,9 +46,7 @@ export default class BuildConfigure extends EasCommand {
'💡 The following process will configure your iOS and/or Android project to be compatible with EAS Build. These changes only apply to your local project files and you can safely revert them at any time.'
);
// BuildConfigure.ContextOptions.Vcs.client.getValueAsync()
await vcsClient.ensureRepoExistsAsync();
await vcsClient.ensureRepoExistsAsync({ nonInteractive: false });
const expoUpdatesIsInstalled = isExpoUpdatesInstalled(projectDir);
+2 -2
View File
@@ -74,8 +74,8 @@ export default class BuildDev extends EasCommand {
Errors.error('Running iOS builds in simulator is only supported on macOS.', { exit: 1 });
}
await vcsClient.ensureRepoExistsAsync();
await ensureRepoIsCleanAsync(vcsClient, flags.nonInteractive);
await vcsClient.ensureRepoExistsAsync({ nonInteractive: false });
await ensureRepoIsCleanAsync(vcsClient, false);
await ensureProjectConfiguredAsync({
projectDir,
nonInteractive: false,
@@ -94,7 +94,7 @@ export default class BuildInspect extends EasCommand {
await this.prepareOutputDirAsync(outputDirectory, flags.force);
if (flags.stage === InspectStage.ARCHIVE) {
await vcsClient.ensureRepoExistsAsync();
await vcsClient.ensureRepoExistsAsync({ nonInteractive: false });
await vcsClient.makeShallowCopyAsync(tmpWorkingdir);
await this.copyToOutputDirAsync(tmpWorkingdir, outputDirectory);
} else {
@@ -1,287 +0,0 @@
import { Flags } from '@oclif/core';
import fs from 'fs-extra';
import jwt from 'jsonwebtoken';
import fetch from 'node-fetch';
import path from 'path';
import { setTimeout } from 'timers/promises';
import { z } from 'zod';
import EasCommand from '../../commandUtils/EasCommand';
import Log from '../../log';
import { AscApiClient, AscApiClientPostApi } from '../../submit/ios/AscApiClient';
export default class SubmitUploadToAsc extends EasCommand {
static override hidden = true;
static override flags = {
path: Flags.string({
description: 'Path to the IPA file',
required: true,
}),
key: Flags.string({
description: 'Path to the ASC API Key JSON file',
required: true,
}),
'app-id': Flags.string({
description: 'App Store Connect App ID (e.g. 1491144534)',
required: true,
}),
'bundle-version': Flags.string({
description: 'CFBundleVersion (Build Version, e.g. 13)',
required: true,
}),
'bundle-short-version': Flags.string({
description: 'CFBundleShortVersionString (Marketing Version, e.g. 1.0.0)',
required: true,
}),
};
async runAsync(): Promise<void> {
const { flags } = await this.parse(SubmitUploadToAsc);
const ipaPath = path.resolve(flags.path);
if (!(await fs.pathExists(ipaPath))) {
throw new Error(`IPA file not found: ${ipaPath}`);
}
const fileSize = (await fs.stat(ipaPath)).size;
const fileName = path.basename(ipaPath);
const keyPath = path.resolve(flags.key);
if (!(await fs.pathExists(keyPath))) {
throw new Error(`Key file not found: ${keyPath}`);
}
const keyJson = await fs.readJson(keyPath);
const apiKey = z
.object({
issuer_id: z.string(),
key_id: z.string(),
key: z.string(),
})
.parse(keyJson);
Log.log('Generating JWT...');
const token = jwt.sign({}, apiKey.key, {
algorithm: 'ES256',
issuer: apiKey.issuer_id,
expiresIn: '20m',
audience: 'appstoreconnect-v1',
header: {
kid: apiKey.key_id,
},
});
const client = new AscApiClient({ token });
Log.log('Reading App information...');
const appResponse = await client.getAsync(
'/v1/apps/:id',
{ 'fields[apps]': ['bundleId', 'name'] as const },
{ id: flags['app-id'] }
);
Log.log(
`Uploading Build to "${appResponse.data.attributes.name}" (${appResponse.data.attributes.bundleId})...`
);
Log.log('Creating Build Upload...');
const buildUploadResponse = await client.postAsync('/v1/buildUploads', {
data: {
type: 'buildUploads',
attributes: {
platform: 'IOS',
cfBundleShortVersionString: flags['bundle-short-version'],
cfBundleVersion: flags['bundle-version'],
},
relationships: {
app: {
data: {
type: 'apps',
id: flags['app-id'],
},
},
},
},
});
const buildUploadId = buildUploadResponse.data.id;
Log.log(`Build Upload initialized (ID: ${buildUploadId}). Preparing IPA upload...`);
const buildFileResponse = await client.postAsync('/v1/buildUploadFiles', {
data: {
type: 'buildUploadFiles',
attributes: {
assetType: 'ASSET',
fileName,
fileSize,
uti: 'com.apple.ipa',
},
relationships: {
buildUpload: {
data: {
type: 'buildUploads',
id: buildUploadId,
},
},
},
},
});
Log.log(`IPA Upload initialized (ID: ${buildFileResponse.data.id}). Uploading IPA...`);
await uploadChunksAsync({
uploadOperations: buildFileResponse.data.attributes.uploadOperations,
ipaPath,
});
Log.log('Committing upload...');
await client.patchAsync(
`/v1/buildUploadFiles/:id`,
{
data: {
type: 'buildUploadFiles',
id: buildFileResponse.data.id,
attributes: {
uploaded: true,
},
},
},
{
id: buildFileResponse.data.id,
}
);
Log.log('Checking upload file status...');
const waitingForFileStartedAt = performance.now();
while (performance.now() - waitingForFileStartedAt < 60 * 1000 /* 60 seconds */) {
const buildFileStatusResponse = await client.getAsync(
`/v1/buildUploadFiles/:id`,
{ 'fields[buildUploadFiles]': ['assetDeliveryState'] },
{ id: buildFileResponse.data.id }
);
if (buildFileStatusResponse.data.attributes.assetDeliveryState.state === 'AWAITING_UPLOAD') {
Log.log(
`Waiting for file upload to finish processing... (state = ${buildFileStatusResponse.data.attributes.assetDeliveryState.state})`
);
await setTimeout(2000);
continue;
}
const { errors = [], warnings = [] } =
buildFileStatusResponse.data.attributes.assetDeliveryState;
if (warnings.length > 0) {
Log.warn(
`Warnings:\n- ${warnings.map(w => `${w.description} (${w.code})`).join('\n- ')}\n`
);
}
if (errors.length > 0) {
Log.error(`Errors:\n- ${errors.map(e => `${e.description} (${e.code})`).join('\n- ')}\n`);
}
if (buildFileStatusResponse.data.attributes.assetDeliveryState.state === 'FAILED') {
throw new Error(`File upload (ID: ${buildFileResponse.data.id}) failed.`);
} else if (buildFileStatusResponse.data.attributes.assetDeliveryState.state === 'COMPLETE') {
Log.log(`File upload (ID: ${buildFileResponse.data.id}) complete!`);
} else if (
buildFileStatusResponse.data.attributes.assetDeliveryState.state === 'UPLOAD_COMPLETE'
) {
Log.log(`File upload (ID: ${buildFileResponse.data.id}) finished!`);
}
break;
}
Log.log('Checking build upload status...');
const waitingForBuildStartedAt = performance.now();
while (performance.now() - waitingForBuildStartedAt < 60 * 1000 /* 60 seconds */) {
const buildUploadStatusResponse = await client.getAsync(
`/v1/buildUploads/:id`,
{ 'fields[buildUploads]': ['state', 'build'], include: ['build'] },
{ id: buildUploadId }
);
if (
buildUploadStatusResponse.data.attributes.state.state === 'AWAITING_UPLOAD' ||
buildUploadStatusResponse.data.attributes.state.state === 'PROCESSING'
) {
Log.log(
`Waiting for build upload to finish... (status = ${buildUploadStatusResponse.data.attributes.state.state})`
);
await setTimeout(2000);
continue;
}
Log.log('\n');
const {
errors = [],
warnings = [],
infos = [],
} = buildUploadStatusResponse.data.attributes.state;
if (infos.length > 0) {
Log.log(`Infos:\n- ${infos.map(i => `${i.description} (${i.code})`).join('\n- ')}\n`);
}
if (warnings.length > 0) {
Log.warn(
`Warnings:\n- ${warnings.map(w => `${w.description} (${w.code})`).join('\n- ')}\n`
);
}
if (errors.length > 0) {
Log.error(`Errors:\n- ${errors.map(e => `${e.description} (${e.code})`).join('\n- ')}\n`);
}
if (buildUploadStatusResponse.data.attributes.state.state === 'FAILED') {
throw new Error(`Build upload (ID: ${buildUploadId}) failed.`);
} else if (buildUploadStatusResponse.data.attributes.state.state === 'COMPLETE') {
Log.log(`Build upload (ID: ${buildUploadId}) complete!`);
}
break;
}
}
}
async function uploadChunksAsync({
uploadOperations,
ipaPath,
}: {
uploadOperations: AscApiClientPostApi['/v1/buildUploadFiles']['response']['data']['attributes']['uploadOperations'];
ipaPath: string;
}): Promise<void> {
const fd = await fs.open(ipaPath, 'r');
try {
const promises = uploadOperations.map(async (op, index) => {
Log.log(
` Uploading chunk ${index + 1}/${uploadOperations.length} (offset: ${op.offset}, length: ${
op.length
})...`
);
const buffer = new Uint8Array(op.length);
await fs.read(fd, buffer, 0, op.length, op.offset);
const headers: Record<string, string> = {
'Content-Type': 'application/octet-stream',
};
for (const { name, value } of op.requestHeaders) {
headers[name] = value;
}
const response = await fetch(op.url, {
method: op.method,
headers,
body: buffer,
});
if (!response.ok) {
const text = await response.text();
throw new Error(
`Failed to upload chunk ${index + 1}: ${response.status} ${response.statusText}\n${text}`
);
}
Log.log(` Chunk ${index + 1}/${uploadOperations.length} uploaded.`);
});
await Promise.all(promises);
} finally {
await fs.close(fd);
}
}
@@ -46,7 +46,7 @@ export default class UpdateConfigure extends EasCommand {
'💡 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.'
);
await vcsClient.ensureRepoExistsAsync();
await vcsClient.ensureRepoExistsAsync({ nonInteractive: flags['non-interactive'] });
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
const easJsonCliConfig: EasJson['cli'] =
@@ -240,7 +240,7 @@ export default class UpdatePublish extends EasCommand {
enableJsonOutput();
}
await vcsClient.ensureRepoExistsAsync();
await vcsClient.ensureRepoExistsAsync({ nonInteractive });
await ensureRepoIsCleanAsync(vcsClient, nonInteractive);
const {
+1 -1
View File
@@ -5,7 +5,7 @@ import fg from 'fast-glob';
import fs from 'fs-extra';
import StreamZip from 'node-stream-zip';
import path from 'path';
import tar from 'tar';
import * as tar from 'tar';
import { v4 as uuidv4 } from 'uuid';
import { getBuildLogsUrl } from '../build/utils/url';
+31 -2
View File
@@ -31,7 +31,22 @@ export async function selectAppleTeamOnAccountAsync(
}
): Promise<AppleTeamFragment> {
if (paginatedQueryOptions.nonInteractive) {
throw new Error('Unable to select an Apple team in non-interactive mode.');
const teams = await AppleTeamQuery.getAllForAccountAsync(graphqlClient, {
accountName,
limit: paginatedQueryOptions.limit ?? TEAMS_LIMIT,
offset: paginatedQueryOptions.offset,
});
if (teams.length === 0) {
throw new Error(`No Apple teams found for account ${accountName}.`);
}
const teamList = teams
.map(t =>
t.appleTeamName ? `${t.appleTeamName} (${t.appleTeamIdentifier})` : t.appleTeamIdentifier
)
.join('\n ');
throw new Error(
`Unable to select an Apple team in non-interactive mode. Use the --apple-team-id flag to specify the team. Available Apple teams for account ${accountName}:\n ${teamList}`
);
} else {
const selectedAppleTeam = await paginatedQueryWithSelectPromptAsync({
limit: paginatedQueryOptions.limit ?? TEAMS_LIMIT,
@@ -74,7 +89,21 @@ export async function selectAppleDeviceOnAppleTeamAsync(
}
): Promise<AppleDeviceFragment> {
if (paginatedQueryOptions.nonInteractive) {
throw new Error('Unable to select an Apple device in non-interactive mode.');
const devices = await AppleDeviceQuery.getAllForAppleTeamAsync(graphqlClient, {
accountName,
appleTeamIdentifier,
limit: paginatedQueryOptions.limit ?? DEVICES_LIMIT,
offset: paginatedQueryOptions.offset,
});
if (devices.length === 0) {
throw new Error(
`No devices found on Apple team ${appleTeamIdentifier} for account ${accountName}.`
);
}
const deviceList = devices.map(d => formatDeviceLabel(d)).join('\n ');
throw new Error(
`Unable to select an Apple device in non-interactive mode. Available devices on Apple team ${appleTeamIdentifier}:\n ${deviceList}`
);
} else {
const selectedAppleDevice = await paginatedQueryWithSelectPromptAsync({
limit: paginatedQueryOptions.limit ?? DEVICES_LIMIT,
@@ -41,7 +41,13 @@ export class ManageRollout implements EASUpdateAction<EASUpdateAction> {
public async runAsync(ctx: EASUpdateContext): Promise<EASUpdateAction> {
const { nonInteractive } = ctx;
if (nonInteractive) {
throw new Error(`rollout selection cannot be run in non-interactive mode.`);
throw new Error(
`Rollout selection cannot be run in non-interactive mode. Available actions: ${[
ManageRolloutActions.EDIT,
ManageRolloutActions.END,
ManageRolloutActions.VIEW,
].join(', ')}.`
);
}
const channelObject = await this.getChannelObjectAsync(ctx);
printRollout(channelObject);
@@ -52,7 +52,20 @@ export class SelectRuntime implements EASUpdateAction<string | null> {
const { nonInteractive, graphqlClient, app } = ctx;
const { projectId } = app;
if (nonInteractive) {
throw new NonInteractiveError(`runtime selection cannot be run in non-interactive mode.`);
const runtimes = await this.getNewestRuntimeAsync(graphqlClient, {
appId: projectId,
branchName: this.branchInfo.name,
anotherBranchIdToIntersectRuntimesBy: this.options.anotherBranchToIntersectRuntimesBy?.id,
});
if (runtimes.edges.length === 0) {
throw new NonInteractiveError(
`No ${this.printedType} versions found on branch "${this.branchInfo.name}".`
);
}
const runtimeList = runtimes.edges.map(e => e.node.version).join('\n ');
throw new NonInteractiveError(
`Runtime selection cannot be run in non-interactive mode. Available ${this.printedType} versions on branch "${this.branchInfo.name}":\n ${runtimeList}`
);
}
const newestRuntimeConnection = await this.getNewestRuntimeAsync(graphqlClient, {
@@ -57,7 +57,7 @@ describe('update queries', () => {
limit: 50,
},
})
).rejects.toThrowError(`Unable to select an update in non-interactive mode.`);
).rejects.toThrowError(`No update groups found on branch "${branchName}".`);
});
});
});
+32 -2
View File
@@ -126,7 +126,18 @@ export async function selectRuntimeAndGetLatestUpdateGroupForEachPublishPlatform
}
): Promise<Record<UpdatePublishPlatform, UpdateFragment[] | undefined>> {
if (paginatedQueryOptions.nonInteractive) {
throw new Error('Unable to select an update in non-interactive mode.');
const runtimes = await RuntimeQuery.getRuntimesOnBranchAsync(graphqlClient, {
appId: projectId,
name: branchName,
first: RUNTIME_VERSIONS_LIMIT,
});
if (runtimes.edges.length === 0) {
throw new Error(`No runtime versions found on branch "${branchName}".`);
}
const runtimeList = runtimes.edges.map(e => e.node.version).join('\n ');
throw new Error(
`Unable to select a runtime in non-interactive mode. Available runtime versions on branch "${branchName}":\n ${runtimeList}`
);
}
const runtimeVersion = await selectRuntimeOnBranchAsync(graphqlClient, {
@@ -178,7 +189,26 @@ export async function selectUpdateGroupOnBranchAsync(
}
): Promise<UpdateFragment[]> {
if (paginatedQueryOptions.nonInteractive) {
throw new Error('Unable to select an update in non-interactive mode.');
const updateGroups = await queryUpdateGroupsOnBranchAsync(graphqlClient, {
appId: projectId,
branchName,
limit: paginatedQueryOptions.limit ?? UPDATE_GROUPS_LIMIT,
offset: paginatedQueryOptions.offset,
});
if (updateGroups.length === 0) {
throw new Error(`No update groups found on branch "${branchName}".`);
}
const updateList = updateGroups
.map(group => {
const first = group[0];
return first
? `${first.group} (runtime: ${first.runtimeVersion}, message: ${first.message ?? 'N/A'})`
: 'unknown';
})
.join('\n ');
throw new Error(
`Unable to select an update in non-interactive mode. Use the --group flag to specify the update group. Available update groups on branch "${branchName}":\n ${updateList}`
);
}
const updateGroup = await paginatedQueryWithSelectPromptAsync({
+8 -5
View File
@@ -4,8 +4,8 @@ import assert from 'assert';
import chalk from 'chalk';
import nullthrows from 'nullthrows';
import { fetchSessionSecretAndSsoUserAsync } from './fetchSessionSecretAndSsoUser';
import { fetchSessionSecretAndUserAsync } from './fetchSessionSecretAndUser';
import { fetchSessionSecretAndUserFromBrowserAuthFlowAsync } from './fetchSessionSecretAndUserFromBrowserAuthFlow';
import { ApiV2Error } from '../ApiV2Error';
import { AnalyticsWithOrchestration } from '../analytics/AnalyticsManager';
import { ApiV2Client } from '../api';
@@ -149,6 +149,7 @@ export default class SessionManager {
nonInteractive = false,
printNewLine = false,
sso = false,
browser = false,
} = {}): Promise<void> {
if (nonInteractive) {
Errors.error(
@@ -164,8 +165,8 @@ export default class SessionManager {
Log.newLine();
}
if (sso) {
await this.ssoLoginAsync();
if (sso || browser) {
await this.browserLoginAsync({ sso });
return;
}
@@ -205,8 +206,10 @@ export default class SessionManager {
}
}
private async ssoLoginAsync(): Promise<void> {
const { sessionSecret, id, username } = await fetchSessionSecretAndSsoUserAsync();
private async browserLoginAsync({ sso = false }): Promise<void> {
const { sessionSecret, id, username } = await fetchSessionSecretAndUserFromBrowserAuthFlowAsync(
{ sso }
);
await this.setSessionAsync({
sessionSecret,
userId: id,
@@ -9,8 +9,8 @@ import Log from '../../log';
import { promptAsync, selectAsync } from '../../prompts';
import { getStateJsonPath } from '../../utils/paths';
import SessionManager, { UserSecondFactorDeviceMethod } from '../SessionManager';
import { fetchSessionSecretAndSsoUserAsync } from '../fetchSessionSecretAndSsoUser';
import { fetchSessionSecretAndUserAsync } from '../fetchSessionSecretAndUser';
import { fetchSessionSecretAndUserFromBrowserAuthFlowAsync } from '../fetchSessionSecretAndUserFromBrowserAuthFlow';
jest.mock('../../prompts');
jest.mock('../../log');
@@ -25,7 +25,7 @@ jest.mock('../../graphql/queries/UserQuery', () => ({
},
}));
jest.mock('../fetchSessionSecretAndUser');
jest.mock('../fetchSessionSecretAndSsoUser');
jest.mock('../fetchSessionSecretAndUserFromBrowserAuthFlow');
jest.mock('../../api');
const authStub: any = {
@@ -156,15 +156,15 @@ describe(SessionManager, () => {
});
});
describe('ssoLoginAsync', () => {
it('saves user data to ~/.expo/state.json', async () => {
jest.mocked(fetchSessionSecretAndSsoUserAsync).mockResolvedValue({
describe('browserLoginAsync', () => {
it('saves user data to ~/.expo/state.json with sso', async () => {
jest.mocked(fetchSessionSecretAndUserFromBrowserAuthFlowAsync).mockResolvedValue({
sessionSecret: 'SESSION_SECRET',
id: 'USER_ID',
username: 'USERNAME',
});
const sessionManager = new SessionManager(analytics);
await sessionManager['ssoLoginAsync']();
await sessionManager['browserLoginAsync']({ sso: true });
expect(await fs.readFile(getStateJsonPath(), 'utf8')).toMatchInlineSnapshot(`
"{
"auth": {
@@ -176,6 +176,31 @@ describe(SessionManager, () => {
}
"
`);
expect(fetchSessionSecretAndUserFromBrowserAuthFlowAsync).toHaveBeenCalledWith({ sso: true });
});
it('saves user data to ~/.expo/state.json without sso', async () => {
jest.mocked(fetchSessionSecretAndUserFromBrowserAuthFlowAsync).mockResolvedValue({
sessionSecret: 'SESSION_SECRET',
id: 'USER_ID',
username: 'USERNAME',
});
const sessionManager = new SessionManager(analytics);
await sessionManager['browserLoginAsync']({ sso: false });
expect(await fs.readFile(getStateJsonPath(), 'utf8')).toMatchInlineSnapshot(`
"{
"auth": {
"sessionSecret": "SESSION_SECRET",
"userId": "USER_ID",
"username": "USERNAME",
"currentConnection": "Browser-Flow-Authentication"
}
}
"
`);
expect(fetchSessionSecretAndUserFromBrowserAuthFlowAsync).toHaveBeenCalledWith({
sso: false,
});
});
});
@@ -292,7 +317,18 @@ describe(SessionManager, () => {
// SSO login
await sessionManager.showLoginPromptAsync({ sso: true });
expect(promptAsync).not.toHaveBeenCalled();
expect(fetchSessionSecretAndSsoUserAsync).toHaveBeenCalled();
expect(fetchSessionSecretAndUserFromBrowserAuthFlowAsync).toHaveBeenCalledWith({ sso: true });
});
it('calls browser login if the browser flag is true', async () => {
const sessionManager = new SessionManager(analytics);
// Browser login (not SSO)
await sessionManager.showLoginPromptAsync({ browser: true });
expect(promptAsync).not.toHaveBeenCalled();
expect(fetchSessionSecretAndUserFromBrowserAuthFlowAsync).toHaveBeenCalledWith({
sso: false,
});
});
});
@@ -4,53 +4,22 @@ import http from 'http';
import { Socket } from 'node:net';
import querystring from 'querystring';
import { getExpoWebsiteBaseUrl } from '../api';
import Log from '../log';
const successBody = `
<!DOCTYPE html>
<html lang="en">
<head>
<title>Expo SSO Login</title>
<meta charset="utf-8">
<style type="text/css">
html {
margin: 0;
padding: 0
}
body {
background-color: #fff;
font-family: Tahoma,Verdana;
font-size: 16px;
color: #000;
max-width: 100%;
box-sizing: border-box;
padding: .5rem;
margin: 1em;
overflow-wrap: break-word
}
</style>
</head>
<body>
SSO login complete. You may now close this tab and return to the command prompt.
</body>
</html>`;
export async function getSessionUsingBrowserAuthFlowAsync({
expoWebsiteUrl,
}: {
expoWebsiteUrl: string;
}): Promise<string> {
export async function getSessionUsingBrowserAuthFlowAsync({ sso = false }): Promise<string> {
const scheme = 'http';
const hostname = 'localhost';
const path = '/auth/callback';
const buildExpoSsoLoginUrl = (port: number): string => {
const data = {
const expoWebsiteUrl = getExpoWebsiteBaseUrl();
const buildExpoLoginUrl = (port: number, sso: boolean): string => {
const params = querystring.stringify({
confirm_account: true,
app_redirect_uri: `${scheme}://${hostname}:${port}${path}`,
};
const params = querystring.stringify(data);
return `${expoWebsiteUrl}/sso-login?${params}`;
});
return `${expoWebsiteUrl}${sso ? '/sso-login' : '/login'}?${params}`;
};
// Start server and begin auth flow
@@ -60,9 +29,19 @@ export async function getSessionUsingBrowserAuthFlowAsync({
const server = http.createServer(
(request: http.IncomingMessage, response: http.ServerResponse) => {
const redirectAndCleanup = (result: 'success' | 'error'): void => {
const redirectUrl = `${expoWebsiteUrl}/oauth/expo-cli?result=${result}`;
response.writeHead(302, { Location: redirectUrl });
response.end();
server.close();
for (const connection of connections) {
connection.destroy();
}
};
try {
if (!(request.method === 'GET' && request.url?.includes('/auth/callback'))) {
throw new Error('Unexpected SSO login response.');
throw new Error('Unexpected login response.');
}
const url = new URL(request.url, `http:${request.headers.host}`);
const sessionSecret = url.searchParams.get('session_secret');
@@ -71,17 +50,10 @@ export async function getSessionUsingBrowserAuthFlowAsync({
throw new Error('Request missing session_secret search parameter.');
}
resolve(sessionSecret);
response.writeHead(200, { 'Content-Type': 'text/html' });
response.write(successBody);
response.end();
redirectAndCleanup('success');
} catch (error) {
redirectAndCleanup('error');
reject(error);
} finally {
server.close();
// Ensure that the server shuts down
for (const connection of connections) {
connection.destroy();
}
}
}
);
@@ -95,7 +67,7 @@ export async function getSessionUsingBrowserAuthFlowAsync({
'Server address and port should be set after listening has begun'
);
const port = address.port;
const authorizeUrl = buildExpoSsoLoginUrl(port);
const authorizeUrl = buildExpoLoginUrl(port, sso);
Log.log(
`If your browser doesn't automatically open, visit this link to log in: ${authorizeUrl}`
);
@@ -1,15 +1,12 @@
import { getSessionUsingBrowserAuthFlowAsync } from './expoSsoLauncher';
import { getSessionUsingBrowserAuthFlowAsync } from './expoBrowserAuthFlowLauncher';
import { fetchUserAsync } from './fetchUser';
import { getExpoWebsiteBaseUrl } from '../api';
export async function fetchSessionSecretAndSsoUserAsync(): Promise<{
export async function fetchSessionSecretAndUserFromBrowserAuthFlowAsync({ sso = false }): Promise<{
sessionSecret: string;
id: string;
username: string;
}> {
const sessionSecret = await getSessionUsingBrowserAuthFlowAsync({
expoWebsiteUrl: getExpoWebsiteBaseUrl(),
});
const sessionSecret = await getSessionUsingBrowserAuthFlowAsync({ sso });
const userData = await fetchUserAsync({ sessionSecret });
+7 -3
View File
@@ -31,7 +31,9 @@ export async function promptVariableTypeAsync(
initialType?: EnvironmentSecretType
): Promise<EnvironmentSecretType> {
if (nonInteractive) {
throw new Error('The `--type` flag must be set when running in `--non-interactive` mode.');
throw new Error(
'The `--type` flag must be set when running in `--non-interactive` mode. Valid values: string, file.'
);
}
const options = [
@@ -89,7 +91,7 @@ export async function promptVariableVisibilityAsync(
): Promise<EnvironmentVariableVisibility> {
if (nonInteractive) {
throw new Error(
'The `--visibility` flag must be set when running in `--non-interactive` mode.'
'The `--visibility` flag must be set when running in `--non-interactive` mode. Valid values: plaintext, sensitive, secret.'
);
}
return await selectAsync('Select visibility:', [
@@ -136,7 +138,9 @@ export async function promptVariableEnvironmentAsync({
}: EnvironmentPromptArgs & { multiple?: boolean }): Promise<string[] | string> {
if (nonInteractive) {
throw new Error(
'The `--environment` flag must be set when running in `--non-interactive` mode.'
`The \`--environment\` flag must be set when running in \`--non-interactive\` mode. Default environments: ${DEFAULT_ENVIRONMENTS.join(
', '
)}.`
);
}
@@ -255,4 +255,27 @@ describe('displayOverageWarning', () => {
expect.objectContaining({ text: 'Upgrade your plan to continue service.' })
);
});
it('shows progress bar when usage is under 100%', () => {
displayOverageWarning({
percentUsed: 85,
hasFreePlan: true,
name: 'test-account',
});
expect(mockWarn).toHaveBeenCalledWith(expect.stringMatching(/█+░+/));
});
it('does not show progress bar when usage is at 100%', () => {
displayOverageWarning({
percentUsed: 100,
hasFreePlan: true,
name: 'test-account',
});
expect(mockWarn).toHaveBeenCalledWith(
expect.stringContaining("You've used 100% of your included build credits for this month.")
);
expect(mockWarn).not.toHaveBeenCalledWith(expect.stringMatching(/[█░]/));
});
});
@@ -61,10 +61,12 @@ export function displayOverageWarning({
hasFreePlan: boolean;
name: string;
}): void {
Log.warn(
chalk.bold(`You've used ${percentUsed}% of your included build credits for this month. `) +
createProgressBar(percentUsed)
const message = chalk.bold(
`You've used ${percentUsed}% of your included build credits for this month.`
);
// Don't show progress bar at 100% - it's redundant when the limit is reached
const progressBar = percentUsed < 100 ? ' ' + createProgressBar(percentUsed) : '';
Log.warn(message + progressBar);
const billingUrl = `https://expo.dev/accounts/${name}/settings/billing`;
const warning = hasFreePlan
+36 -11
View File
@@ -31,7 +31,11 @@ export default class GitClient extends Client {
this.requireCommit = options.requireCommit;
}
public override async ensureRepoExistsAsync(): Promise<void> {
public override async ensureRepoExistsAsync(options?: {
nonInteractive?: boolean;
}): Promise<void> {
const nonInteractive = options?.nonInteractive ?? false;
try {
if (!(await isGitInstalledAsync())) {
Log.error(
@@ -66,11 +70,26 @@ export default class GitClient extends Client {
return;
}
const cwd = process.cwd();
const repoRoot = PackageManagerUtils.resolveWorkspaceRoot(cwd) ?? cwd;
if (nonInteractive) {
Log.log(
`No git repository found. Auto initializing in ${this.maybeCwdOverride ?? repoRoot}. ` +
`Set ${chalk.bold('EAS_NO_VCS=1')} to skip.`
);
await spawnAsync('git', ['init'], { cwd: this.maybeCwdOverride ?? repoRoot });
await this.commitAsync({
commitAllFiles: true,
commitMessage: 'Initial commit',
nonInteractive: true,
});
return;
}
Log.warn("It looks like you haven't initialized the git repository yet.");
Log.warn('EAS requires you to use a git repository for your project.');
const cwd = process.cwd();
const repoRoot = PackageManagerUtils.resolveWorkspaceRoot(cwd) ?? cwd;
const confirmInit = await confirmAsync({
message: `Would you like us to run 'git init' in ${
this.maybeCwdOverride ?? repoRoot
@@ -419,16 +438,22 @@ export default class GitClient extends Client {
return;
}
Log.warn(
`You need to configure Git with your ${[
!usernameConfigured && 'username (user.name)',
!emailConfigured && 'email address (user.email)',
const missingConfig = [
!usernameConfigured && 'username (user.name)',
!emailConfigured && 'email address (user.email)',
].filter(i => i);
Log.warn(`You need to configure Git with your ${missingConfig.join(' and ')}`);
if (nonInteractive) {
const configCommands = [
!usernameConfigured && 'git config --global user.name "Your Name"',
!emailConfigured && 'git config --global user.email "[email protected]"',
]
.filter(i => i)
.join(' and ')}`
);
if (nonInteractive) {
throw new Error('Git cannot be configured automatically in non-interactive mode');
.join(' && ');
throw new Error(
`Git ${missingConfig.join(' and ')} must be configured. Run: ${configCommands}`
);
}
if (!usernameConfigured) {
const { username } = await promptAsync({
+1 -1
View File
@@ -14,7 +14,7 @@ export abstract class Client {
// (optional) ensureRepoExistsAsync should verify whether repository exists and tooling is installed
// it's not required for minimal support, but lack of validation might cause the failure at a later stage.
public async ensureRepoExistsAsync(): Promise<void> {}
public async ensureRepoExistsAsync(_options?: { nonInteractive?: boolean }): Promise<void> {}
// (optional) checks whether commit is necessary before calling makeShallowCopyAsync
//
+4 -3
View File
@@ -1,12 +1,13 @@
import type { Config } from 'jest';
import type { Config } from '@jest/types';
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../jest/jest.config';
import baseConfig from '../../jest/jest.shared.config';
const config: Config = {
const config: Config.InitialOptions = {
...baseConfig,
rootDir: __dirname,
roots: ['src', '__mocks__'],
testRegex: '/__tests__/.*(test|spec)\\.[jt]sx?$',
};
export default config;
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "@expo/eas-json",
"description": "A library for interacting with eas.json",
"version": "16.30.0",
"version": "17.0.0",
"author": "Expo <[email protected]>",
"bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": {
"@babel/code-frame": "7.23.5",
"@expo/eas-build-job": "1.0.265",
"@expo/eas-build-job": "17.0.0",
"chalk": "4.1.2",
"env-string": "1.0.1",
"fs-extra": "11.2.0",
+7 -10
View File
@@ -5,7 +5,7 @@
"url": "https://github.com/expo/eas-cli.git",
"directory": "packages/local-build-plugin"
},
"version": "1.0.272",
"version": "17.0.0",
"description": "Tool for running EAS compatible builds on a local machine.",
"main": "dist/main.js",
"files": [
@@ -21,6 +21,7 @@
"build": "tsc",
"typecheck": "tsc",
"prepack": "rm -rf dist && tsc -p tsconfig.build.json",
"test": "jest --passWithNoTests",
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
},
"engines": {
@@ -30,11 +31,11 @@
"bugs": "https://github.com/expo/eas-cli/issues",
"license": "BUSL-1.1",
"dependencies": {
"@expo/build-tools": "1.0.272",
"@expo/eas-build-job": "1.0.265",
"@expo/logger": "1.0.260",
"@expo/build-tools": "17.0.0",
"@expo/eas-build-job": "17.0.0",
"@expo/logger": "17.0.0",
"@expo/spawn-async": "^1.7.2",
"@expo/turtle-spawn": "1.0.260",
"@expo/turtle-spawn": "17.0.0",
"bunyan": "^1.8.15",
"chalk": "4.1.2",
"env-paths": "^2.2.1",
@@ -43,7 +44,7 @@
"lodash": "^4.17.21",
"nullthrows": "^1.1.1",
"semver": "^7.6.2",
"tar": "^7.2.0",
"tar": "7.5.7",
"uuid": "^9.0.1"
},
"devDependencies": {
@@ -56,9 +57,5 @@
"jest": "^29.7.0",
"ts-jest": "^29.1.4",
"typescript": "^5.5.4"
},
"volta": {
"node": "20.14.0",
"yarn": "1.22.21"
}
}
-8
View File
@@ -1,8 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: 'src',
testMatch: ['**/__tests__/*.test.ts'],
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
+14
View File
@@ -0,0 +1,14 @@
import type { Config } from '@jest/types';
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../jest/jest.shared.config';
const config: Config.InitialOptions = {
...baseConfig,
rootDir: 'src',
testMatch: ['**/__tests__/*.test.ts'],
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
export default config;
+3 -7
View File
@@ -5,7 +5,7 @@
"url": "https://github.com/expo/eas-cli.git",
"directory": "packages/logger"
},
"version": "1.0.260",
"version": "17.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
@@ -17,8 +17,8 @@
"build": "tsc",
"typecheck": "tsc",
"prepack": "rm -rf dist && tsc -p tsconfig.build.json",
"test": "jest --config jest.config.js --passWithNoTests",
"test:watch": "jest --config jest.config.js --watch",
"test": "jest --config jest.config.ts --passWithNoTests",
"test:watch": "jest --config jest.config.ts --watch",
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
},
"author": "Expo <[email protected]>",
@@ -34,9 +34,5 @@
"jest": "^29.7.0",
"ts-jest": "^29.1.4",
"typescript": "^5.5.4"
},
"volta": {
"node": "20.14.0",
"yarn": "1.22.21"
}
}
@@ -1,6 +1,10 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
import type { Config } from '@jest/types';
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../jest/jest.shared.config';
const config: Config.InitialOptions = {
...baseConfig,
rootDir: 'src',
testMatch: ['**/__tests__/*-test.ts'],
coverageReporters: ['json', 'lcov'],
@@ -9,3 +13,5 @@ module.exports = {
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
export default config;
+5 -9
View File
@@ -5,7 +5,7 @@
"url": "https://github.com/expo/eas-cli.git",
"directory": "packages/steps"
},
"version": "1.0.271",
"version": "17.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
@@ -18,8 +18,8 @@
"typecheck": "tsc",
"prepack": "rm -rf dist && tsc --project tsconfig.build.json",
"watch": "tsc --watch --preserveWatchOutput",
"test": "jest --config jest.config.cjs",
"test:coverage": "jest --config jest.config.cjs --coverage",
"test": "jest --config jest.config.ts",
"test:coverage": "jest --config jest.config.ts --coverage",
"test:watch": "yarn test --watch",
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
},
@@ -43,8 +43,8 @@
"node": ">=18"
},
"dependencies": {
"@expo/eas-build-job": "1.0.265",
"@expo/logger": "1.0.260",
"@expo/eas-build-job": "17.0.0",
"@expo/logger": "17.0.0",
"@expo/spawn-async": "^1.7.2",
"arg": "^5.0.2",
"fs-extra": "^11.2.0",
@@ -54,9 +54,5 @@
"lodash.get": "^4.4.2",
"uuid": "^9.0.1",
"yaml": "^2.4.3"
},
"volta": {
"node": "20.14.0",
"yarn": "1.22.21"
}
}
-8
View File
@@ -1,8 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: 'src',
testMatch: ['**/__tests__/*.test.ts'],
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
+14
View File
@@ -0,0 +1,14 @@
import type { Config } from '@jest/types';
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../jest/jest.shared.config';
const config: Config.InitialOptions = {
...baseConfig,
rootDir: 'src',
testMatch: ['**/__tests__/*.test.ts'],
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
export default config;
+3 -7
View File
@@ -5,7 +5,7 @@
"url": "https://github.com/expo/eas-cli.git",
"directory": "packages/template-file"
},
"version": "1.0.260",
"version": "17.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
@@ -17,8 +17,8 @@
"build": "tsc",
"typecheck": "tsc",
"prepack": "rm -rf dist && tsc -p tsconfig.build.json",
"test": "jest --config jest.config.js",
"test:watch": "jest --config jest.config.js --watch",
"test": "jest --config jest.config.ts",
"test:watch": "jest --config jest.config.ts --watch",
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
},
"author": "Expo <[email protected]>",
@@ -35,9 +35,5 @@
"jest": "^29.7.0",
"ts-jest": "^29.1.4",
"typescript": "^5.5.4"
},
"volta": {
"node": "20.14.0",
"yarn": "1.22.21"
}
}
-8
View File
@@ -1,8 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: 'src',
testMatch: ['**/__tests__/*.test.ts'],
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
+14
View File
@@ -0,0 +1,14 @@
import type { Config } from '@jest/types';
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../jest/jest.shared.config';
const config: Config.InitialOptions = {
...baseConfig,
rootDir: 'src',
testMatch: ['**/__tests__/*.test.ts'],
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
export default config;
+4 -8
View File
@@ -5,7 +5,7 @@
"url": "https://github.com/expo/eas-cli.git",
"directory": "packages/turtle-spawn"
},
"version": "1.0.260",
"version": "17.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
@@ -17,15 +17,15 @@
"build": "tsc",
"typecheck": "tsc",
"prepack": "rm -rf dist && tsc -p tsconfig.build.json",
"test": "jest --config jest.config.js --passWithNoTests",
"test:watch": "jest --config jest.config.js --watch",
"test": "jest --config jest.config.ts --passWithNoTests",
"test:watch": "jest --config jest.config.ts --watch",
"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": {
"@expo/logger": "1.0.260",
"@expo/logger": "17.0.0",
"@expo/spawn-async": "^1.7.2"
},
"devDependencies": {
@@ -34,9 +34,5 @@
"jest": "^29.7.0",
"ts-jest": "^29.1.4",
"typescript": "^5.5.4"
},
"volta": {
"node": "20.14.0",
"yarn": "1.22.21"
}
}
@@ -16,6 +16,8 @@ jobs:
steps:
- uses: eas/checkout
- uses: eas/install_node_modules
- name: Build Android worker tarball
run: ./package.sh worker-android.tar.gz android
@@ -33,6 +35,8 @@ jobs:
steps:
- uses: eas/checkout
- uses: eas/install_node_modules
- name: Build iOS worker tarball
run: ./package.sh worker-ios.tar.gz ios
-2
View File
@@ -5,5 +5,3 @@ export PORT="3005"
export WORKER_PORT="3005"
export WORKER_IP="localhost"
export LOGGER_LEVEL="debug"
unset _VOLTA_TOOL_RECURSION
@@ -1,6 +1,10 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
import type { Config } from '@jest/types';
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../jest/jest.shared.config';
const config: Config.InitialOptions = {
...baseConfig,
rootDir: 'src',
testMatch: ['**/__integration__/*.test.ts'],
collectCoverage: true,
@@ -13,3 +17,5 @@ module.exports = {
// WebSocket cleanup timing) may keep handles open that prevent clean exit.
forceExit: true,
};
export default config;
+9 -5
View File
@@ -1,10 +1,14 @@
import type { Config } from 'jest';
import type { Config } from '@jest/types';
export default {
preset: 'ts-jest',
testEnvironment: 'node',
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../jest/jest.shared.config';
const config: Config.InitialOptions = {
...baseConfig,
rootDir: __dirname,
testMatch: ['**/__unit__/*.test.ts'],
clearMocks: true,
setupFilesAfterEnv: ['./jest/unit-setup.ts'],
} satisfies Config;
};
export default config;
+4
View File
@@ -9,6 +9,10 @@ process.env.WORKER_RUNTIME_CONFIG_BASE64 = 'eyAiaW1hZ2UiOiAiIiB9'; // { "image":
process.env.__API_SERVER_URL = 'http://api.expo.test';
process.env.PORT = '3015';
jest.mock('fs');
jest.mock('fs/promises');
jest.mock('node:fs', () => jest.requireMock('fs'));
// Always mock:
jest.mock('@expo/build-tools', () => {
return {
+11 -11
View File
@@ -1,6 +1,6 @@
{
"name": "@expo/worker",
"version": "16.32.0",
"version": "17.0.0",
"private": true,
"scripts": {
"start": "yarn watch",
@@ -13,8 +13,8 @@
"test": "yarn test:unit",
"test:unit": "./resources/jest --config jest.config.unit.ts",
"test:unit:watch": "./resources/jest --config jest.config.unit.ts --watch",
"test:integration": "./resources/jest --runInBand --config jest.config.integration.js",
"test:integration:watch": "./resources/jest --runInBand --config jest.config.integration.js --watchAll",
"test:integration": "./resources/jest --runInBand --config jest.config.integration.ts",
"test:integration:watch": "./resources/jest --runInBand --config jest.config.integration.ts --watchAll",
"typecheck": "tsc"
},
"engines": {
@@ -22,18 +22,18 @@
},
"dependencies": {
"@babel/core": "7.24.7",
"@expo/build-tools": "1.0.272",
"@expo/build-tools": "17.0.0",
"@expo/config-plugins": "8.0.8",
"@expo/config-types": "52.0.1",
"@expo/downloader": "1.0.260",
"@expo/eas-build-job": "1.0.265",
"@expo/logger": "1.0.260",
"@expo/downloader": "17.0.0",
"@expo/eas-build-job": "17.0.0",
"@expo/logger": "17.0.0",
"@expo/results": "1.0.0",
"@expo/rudder-sdk-node": "1.1.1",
"@expo/spawn-async": "1.7.2",
"@expo/steps": "1.0.271",
"@expo/template-file": "1.0.260",
"@expo/turtle-spawn": "1.0.260",
"@expo/steps": "17.0.0",
"@expo/template-file": "17.0.0",
"@expo/turtle-spawn": "17.0.0",
"@hapi/boom": "10.0.1",
"@sentry/node": "7.77.0",
"check-disk-space": "3.4.0",
@@ -50,7 +50,7 @@
"node-os-utils": "1.3.7",
"nullthrows": "1.1.1",
"semver": "7.6.3",
"tar": "6.2.1",
"tar": "7.5.7",
"uuid": "3.4.0",
"ws": "8.18.0",
"xml2js": "0.6.2",
+16 -5
View File
@@ -33,9 +33,16 @@ copy_package() {
dst=$2
mkdir -p $dst
cp "$src/package.json" "$dst/package.json"
cp "$src/tsconfig.json" "$dst/tsconfig.json"
cp "$src/tsconfig.build.json" "$dst/tsconfig.build.json"
if [[ -f "$src/tsconfig.json" ]]; then
cp "$src/tsconfig.json" "$dst/tsconfig.json"
fi
if [[ -f "$src/tsconfig.build.json" ]]; then
cp "$src/tsconfig.build.json" "$dst/tsconfig.build.json"
fi
cp -r "$src/src" "$dst/src"
if [[ -d "$src/bin" ]]; then
cp -r "$src/bin" "$dst/bin"
fi
}
cp "$ROOT_DIR/yarn.lock" $target_root_dir
@@ -43,11 +50,16 @@ cp "$ROOT_DIR/package.json" $target_root_dir
cp "$ROOT_DIR/lerna.json" $target_root_dir
copy_package $WORKER_DIR $target_worker_dir
while IFS= read -r package_dir; do
package_name=$(basename "$package_dir")
copy_package "$package_dir" "$target_root_dir/packages/$package_name"
done <<< "$(yarn --silent lerna list --scope "@expo/worker" --include-dependencies --parseable --loglevel silent)"
pushd $target_root_dir >/dev/null 2>&1
yarn install --silent
yarn install --silent --frozen-lockfile
yarn build
yarn install --silent --production=true
yarn install --silent --production=true --frozen-lockfile
rm -rf tsconfig.json tsconfig.build.json
popd >/dev/null 2>&1
@@ -65,7 +77,6 @@ if [[ "$PLATFORM" == "ios" ]]; then
mv expo-cocoapods-proxy-*.gem "$target_worker_dir/expo-cocoapods-proxy.gem"
popd >/dev/null 2>&1
fi
rm -rf $target_root_dir/.volta/
# Create backward-compatible symlink for orchestrator
# The orchestrator expects ./src/services/worker/dist/main.js
+2 -2
View File
@@ -4,7 +4,7 @@ import { BuildJob } from '@expo/eas-build-job';
import filesize from 'filesize';
import fs from 'fs-extra';
import path from 'path';
import tar from 'tar';
import * as tar from 'tar';
import config from './config';
@@ -87,7 +87,7 @@ export class GCSCacheManager implements CacheManager {
this.skipCacheUpdate = true; // if restore failed we don't want to update cache with new values
return;
}
// @ts-expect-error
await tar.extract(
{
file: archivePath,
+114 -96
View File
@@ -3,13 +3,18 @@ import downloadFile from '@expo/downloader';
import { Cache, Platform } from '@expo/eas-build-job';
import { randomUUID } from 'crypto';
import fs from 'fs-extra';
import { vol } from 'memfs';
import os from 'os';
import path from 'path';
import { Readable } from 'stream';
import { GCSCacheManager } from '../CacheManager';
// These tests use real fs and directories in os.tmpdir().
// Something about tar v7 makes mocking fs with memfs problematic.
// Mocking all variants of fs/node:fs/fs/promises does not help.
// Last Codex research indicated it might be something related to fs-minipass
// and fs.writev, mocking fs.writev did not help though.
jest.unmock('fs');
jest.unmock('node:fs');
jest.mock('fs');
jest.mock('fs/promises');
jest.mock('@expo/downloader', () => {
return jest.fn();
});
@@ -23,59 +28,67 @@ jest.mock('../sentry', () => ({
handleError: jest.fn(),
}));
async function saveAndRestoreCacheAsync(
setupBeforeSave: () => Promise<void> | void,
setupBeforeRestore: () => Promise<void> | void,
cacheConfig: Partial<Cache>
): Promise<void> {
await setupBeforeSave();
const manager = new GCSCacheManager();
const mockCtx = {
logger: { info: jest.fn(), error: console.error },
workingdir: '/projectRoot',
buildDirectory: '/projectRoot/build',
job: {
platform: Platform.ANDROID,
projectRootDirectory: '.',
cache: cacheConfig,
},
} as any;
let tarReadStream: Readable;
jest.mocked(GCS.uploadWithSignedUrl).mockImplementation(async ({ srcGeneratorAsync }) => {
const result = await srcGeneratorAsync();
tarReadStream = result;
return randomUUID();
});
await manager.saveCache(mockCtx);
const chunks = [];
for await (const chunk of tarReadStream!) {
chunks.push(chunk);
}
const tarContent = Buffer.concat(chunks);
vol.reset();
await setupBeforeRestore();
jest.mocked(downloadFile).mockImplementation(async (_url, archivePath: string) => {
await fs.mkdirp(mockCtx.workingdir);
await fs.writeFile(archivePath, tarContent);
});
await manager.restoreCache(mockCtx);
}
const { GCSCacheManager } = require('../CacheManager');
describe(GCSCacheManager, () => {
beforeEach(() => {
vol.reset();
let tmpDir: string;
let outsideDir: string;
beforeEach(async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-test-'));
outsideDir = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-outside-test-'));
});
afterEach(() => {
afterEach(async () => {
await fs.rm(tmpDir, { recursive: true, force: true });
await fs.rm(outsideDir, { recursive: true, force: true });
jest.mocked(GCS.uploadWithSignedUrl).mockReset();
jest.mocked(downloadFile).mockReset();
});
async function saveAndRestoreCacheAsync(
setupBeforeSave: () => Promise<void> | void,
setupBeforeRestore: () => Promise<void> | void,
cacheConfig: Partial<Cache>
): Promise<void> {
await setupBeforeSave();
const manager = new GCSCacheManager();
const mockCtx = {
logger: { info: jest.fn(), error: console.error },
workingdir: tmpDir,
buildDirectory: `${tmpDir}/build`,
job: {
platform: Platform.ANDROID,
projectRootDirectory: '.',
cache: cacheConfig,
},
} as any;
let tarReadStream: Readable;
jest.mocked(GCS.uploadWithSignedUrl).mockImplementation(async ({ srcGeneratorAsync }) => {
const result = await srcGeneratorAsync();
tarReadStream = result;
return randomUUID();
});
await manager.saveCache(mockCtx);
const chunks = [];
for await (const chunk of tarReadStream!) {
chunks.push(chunk);
}
const tarContent = Buffer.concat(chunks);
await fs.rm(tmpDir, { recursive: true, force: true });
await fs.rm(outsideDir, { recursive: true, force: true });
await setupBeforeRestore();
jest.mocked(downloadFile).mockImplementation(async (_url, archivePath: string) => {
await fs.mkdirp(mockCtx.workingdir);
await fs.writeFile(archivePath, tarContent);
});
await manager.restoreCache(mockCtx);
}
test('save and restore for a single file', async () => {
await saveAndRestoreCacheAsync(
() => {
vol.fromJSON({
'/projectRoot/build/index.ts': 'index.ts',
});
return fs.outputFile(path.join(tmpDir, 'build', 'index.ts'), 'index.ts');
},
() => {},
{
@@ -83,18 +96,18 @@ describe(GCSCacheManager, () => {
}
);
expect(vol.toJSON()).toStrictEqual({
'/projectRoot/build/index.ts': 'index.ts',
'/projectRoot/cache-restore.tar.gz': expect.anything(),
});
await expect(fs.readFile(path.join(tmpDir, 'build', 'index.ts'), 'utf8')).resolves.toBe(
'index.ts'
);
await expect(fs.pathExists(path.join(tmpDir, 'cache-restore.tar.gz'))).resolves.toBe(true);
});
test('save and restore for a single directory', async () => {
await saveAndRestoreCacheAsync(
() => {
vol.fromJSON({
'/projectRoot/build/src/index.ts': 'index.ts',
'/projectRoot/build/src/main.ts': 'index.ts',
});
async () => {
await Promise.all([
fs.outputFile(path.join(tmpDir, 'build', 'src', 'index.ts'), 'index.ts'),
fs.outputFile(path.join(tmpDir, 'build', 'src', 'main.ts'), 'index.ts'),
]);
},
() => {},
{
@@ -102,73 +115,78 @@ describe(GCSCacheManager, () => {
}
);
expect(vol.toJSON()).toStrictEqual({
'/projectRoot/build/src/index.ts': 'index.ts',
'/projectRoot/build/src/main.ts': 'index.ts',
'/projectRoot/cache-restore.tar.gz': expect.anything(),
});
await expect(fs.readFile(path.join(tmpDir, 'build', 'src', 'index.ts'), 'utf8')).resolves.toBe(
'index.ts'
);
await expect(fs.readFile(path.join(tmpDir, 'build', 'src', 'main.ts'), 'utf8')).resolves.toBe(
'index.ts'
);
await expect(fs.pathExists(path.join(tmpDir, 'cache-restore.tar.gz'))).resolves.toBe(true);
});
test('save and restore for a single directory outside of the project structure', async () => {
await saveAndRestoreCacheAsync(
() => {
vol.fromJSON({
'/projectRoot/build/src/index.ts': 'index.ts',
'/outsideProjectRoot/build/src/index.ts': 'index.ts',
});
async () => {
await Promise.all([
fs.outputFile(path.join(tmpDir, 'build', 'src', 'index.ts'), 'index.ts'),
fs.outputFile(path.join(outsideDir, 'build', 'src', 'index.ts'), 'index.ts'),
]);
},
() => {},
{
paths: ['/outsideProjectRoot/build'],
paths: [path.join(outsideDir, 'build')],
}
);
expect(vol.toJSON()).toStrictEqual({
'/outsideProjectRoot/build/src/index.ts': 'index.ts',
'/projectRoot/cache-restore.tar.gz': expect.anything(),
});
await expect(
fs.readFile(path.join(outsideDir, 'build', 'src', 'index.ts'), 'utf8')
).resolves.toBe('index.ts');
await expect(fs.pathExists(path.join(tmpDir, 'build', 'src', 'index.ts'))).resolves.toBe(false);
await expect(fs.pathExists(path.join(tmpDir, 'cache-restore.tar.gz'))).resolves.toBe(true);
});
test('that save and restore does not override existing files', async () => {
await saveAndRestoreCacheAsync(
() => {
vol.fromJSON({
'/projectRoot/build/src/index.ts': 'index.ts',
'/projectRoot/build/src/main': 'index.ts',
});
async () => {
await Promise.all([
fs.outputFile(path.join(tmpDir, 'build', 'src', 'index.ts'), 'index.ts'),
fs.outputFile(path.join(tmpDir, 'build', 'src', 'main'), 'index.ts'),
]);
},
() => {
vol.fromJSON({
'/projectRoot/build/src/index.ts': 'index2.ts',
});
return fs.outputFile(path.join(tmpDir, 'build', 'src', 'index.ts'), 'index2.ts');
},
{
paths: ['src'],
}
);
expect(vol.toJSON()).toStrictEqual({
'/projectRoot/build/src/index.ts': 'index2.ts',
'/projectRoot/build/src/main': 'index.ts',
'/projectRoot/cache-restore.tar.gz': expect.anything(),
});
await expect(fs.readFile(path.join(tmpDir, 'build', 'src', 'index.ts'), 'utf8')).resolves.toBe(
'index2.ts'
);
await expect(fs.readFile(path.join(tmpDir, 'build', 'src', 'main'), 'utf8')).resolves.toBe(
'index.ts'
);
await expect(fs.pathExists(path.join(tmpDir, 'cache-restore.tar.gz'))).resolves.toBe(true);
});
test('save and restore for multiple locations', async () => {
await saveAndRestoreCacheAsync(
() => {
vol.fromJSON({
'/projectRoot/build/src/index.ts': 'index.ts',
'/outsideProjectRoot/build/src/index.ts': 'index.ts',
});
async () => {
await Promise.all([
fs.outputFile(path.join(tmpDir, 'build', 'src', 'index.ts'), 'index.ts'),
fs.outputFile(path.join(outsideDir, 'build', 'src', 'index.ts'), 'index.ts'),
]);
},
() => {},
{
paths: ['src', '/outsideProjectRoot/build'],
paths: ['src', path.join(outsideDir, 'build')],
}
);
expect(vol.toJSON()).toStrictEqual({
'/outsideProjectRoot/build/src/index.ts': 'index.ts',
'/projectRoot/build/src/index.ts': 'index.ts',
'/projectRoot/cache-restore.tar.gz': expect.anything(),
});
await expect(fs.readFile(path.join(tmpDir, 'build', 'src', 'index.ts'), 'utf8')).resolves.toBe(
'index.ts'
);
await expect(
fs.readFile(path.join(outsideDir, 'build', 'src', 'index.ts'), 'utf8')
).resolves.toBe('index.ts');
await expect(fs.pathExists(path.join(tmpDir, 'cache-restore.tar.gz'))).resolves.toBe(true);
});
});
+1 -1
View File
@@ -5,7 +5,7 @@ import { asyncResult } from '@expo/results';
import fs from 'fs-extra';
import nullthrows from 'nullthrows';
import path from 'path';
import tar from 'tar';
import * as tar from 'tar';
import { z } from 'zod';
import config from './config';
Generated
+18 -36
View File
@@ -4843,13 +4843,6 @@
dependencies:
"@types/node" "*"
"@types/[email protected]":
version "8.5.1"
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#56958cb2d80f6d74352bd2e501a018e2506a8a84"
integrity sha512-rNAPdomlIUX0i0cg2+I+Q1wOUr531zHBQ+cV/28PJ39bSPKjahatZZ2LMuhiguETkCgLVzfruw/ZvNMNkKoSzw==
dependencies:
"@types/node" "*"
"@types/keygrip@*":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.6.tgz#1749535181a2a9b02ac04a797550a8787345b740"
@@ -6323,7 +6316,7 @@ [email protected]:
dependencies:
node-int64 "^0.4.0"
[email protected], buffer-equal-constant-time@^1.0.1:
buffer-equal-constant-time@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
@@ -11505,6 +11498,11 @@ join-component@^1.1.0:
resolved "https://registry.yarnpkg.com/join-component/-/join-component-1.1.0.tgz#b8417b750661a392bee2c2537c68b2a9d4977cd5"
integrity sha1-uEF7dQZho5K+4sJTfGiyqdSXfNU=
[email protected]:
version "5.10.0"
resolved "https://registry.yarnpkg.com/jose/-/jose-5.10.0.tgz#c37346a099d6467c401351a9a0c2161e0f52c4be"
integrity sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==
jose@^5.0.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/jose/-/jose-5.2.0.tgz#d0ffd7f7e31253f633eefb190a930cd14a916995"
@@ -11690,16 +11688,6 @@ jsonparse@^1.2.0, jsonparse@^1.3.1:
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=
[email protected]:
version "9.0.0"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d"
integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==
dependencies:
jws "^3.2.2"
lodash "^4.17.21"
ms "^2.1.1"
semver "^7.3.8"
"jsx-ast-utils@^2.4.1 || ^3.0.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82"
@@ -11728,15 +11716,6 @@ just-diff@^6.0.0:
resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-6.0.2.tgz#03b65908543ac0521caf6d8eb85035f7d27ea285"
integrity sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==
jwa@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
dependencies:
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jwa@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.1.tgz#bf8176d1ad0cd72e0f3f58338595a13e110bc804"
@@ -11746,14 +11725,6 @@ jwa@^2.0.0:
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
dependencies:
jwa "^1.4.1"
safe-buffer "^5.0.1"
jws@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4"
@@ -16325,7 +16296,18 @@ [email protected], tar@^6.0.2, tar@^6.1.0, tar@^6.1.11, tar@^6.1.2:
mkdirp "^1.0.3"
yallist "^4.0.0"
tar@^7.2.0, tar@^7.4.3:
tar@7.5.7:
version "7.5.7"
resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.7.tgz#adf99774008ba1c89819f15dbd6019c630539405"
integrity sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==
dependencies:
"@isaacs/fs-minipass" "^4.0.0"
chownr "^3.0.0"
minipass "^7.1.2"
minizlib "^3.1.0"
yallist "^5.0.0"
tar@^7.4.3:
version "7.5.2"
resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.2.tgz#115c061495ec51ff3c6745ff8f6d0871c5b1dedc"
integrity sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==