Compare commits

..
Author SHA1 Message Date
Stanisław Chmiela 7fd0e1b3dc Clarify worker workflow changes 2026-02-03 16:31:50 +01:00
100 changed files with 1045 additions and 1279 deletions

No files matched your search

-11
View File
@@ -1,11 +0,0 @@
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: ./.github/actions/setup-mise
- uses: volta-cli/action@5c175f92dea6f48441c436471e6479dbc192e194 # v4
- uses: ./.github/internal-actions/setup-gcloud
+17 -5
View File
@@ -30,7 +30,9 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-mise
- uses: actions/setup-node@v4
with:
node-version: 22
- run: yarn install --frozen-lockfile --check-files
- run: yarn build
- name: Ensure GraphQL schema and generated code is up-to-date
@@ -75,7 +77,9 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-mise
- uses: actions/setup-node@v4
with:
node-version: 22
- run: brew install python-setuptools
env:
HOMEBREW_NO_AUTO_UPDATE: 1
@@ -118,7 +122,9 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-mise
- uses: actions/setup-node@v4
with:
node-version: 22
- run: yarn install --frozen-lockfile --check-files
- run: sudo apt-get install nsis
- run: yarn build
@@ -158,7 +164,11 @@ jobs:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-mise
- uses: actions/setup-node@v4
with:
registry-url: 'https://registry.npmjs.org/'
scope: 'expo'
node-version: 22
- name: Install dependencies
run: yarn install --frozen-lockfile --check-files
- name: Build
@@ -189,7 +199,9 @@ jobs:
- uses: actions/checkout@v4
with:
token: ${{ secrets.EXPO_BOT_PAT }}
- uses: ./.github/actions/setup-mise
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install dependencies
working-directory: ./scripts
run: yarn install --frozen-lockfile --check-files
+3 -1
View File
@@ -10,7 +10,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/setup-mise
- uses: actions/setup-node@v2
with:
node-version: 20
- uses: preactjs/compressed-size-action@v2
with:
build-script: 'build:tarballs:linux'
+4 -2
View File
@@ -18,8 +18,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup tools
uses: ./.github/actions/setup-mise
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: 22
- run: yarn install --frozen-lockfile
- run: yarn verify-graphql-code
working-directory: packages/eas-cli
+4 -2
View File
@@ -22,7 +22,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup tools
uses: ./.github/actions/setup-mise
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: 20
- run: yarn install --frozen-lockfile --check-files
- run: yarn typecheck
+3 -17
View File
@@ -31,26 +31,12 @@ jobs:
- run: |
git config --global user.email "[email protected]"
git config --global user.name "Test"
- 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
- run: yarn test
if: ${{ !matrix.coverage }}
env:
NODE_OPTIONS: --max-old-space-size=8192
- 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.
- run: yarn test --coverage
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.
+3 -1
View File
@@ -27,7 +27,9 @@ jobs:
ref: main
fetch-depth: 0
token: ${{ secrets.EXPO_BOT_PAT }}
- uses: ./.github/actions/setup-mise
- uses: actions/setup-node@v4
with:
node-version: 22
- run: |
# Lerna will not detect changes correctly without having all tags
git fetch --depth=1 origin +refs/tags/*:refs/tags/*
@@ -0,0 +1,81 @@
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: ./.github/actions/setup-mise
- uses: volta-cli/action@5c175f92dea6f48441c436471e6479dbc192e194 # v4
- name: Create test app
run: npx -y create-expo-app@latest ${{ runner.temp }}/test-app --template tabs
+76 -18
View File
@@ -3,35 +3,88 @@ name: worker
on:
push:
branches: [main]
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
pull_request:
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: ./.github/actions/setup-mise
- uses: volta-cli/action@5c175f92dea6f48441c436471e6479dbc192e194 # v4
- run: yarn install --frozen-lockfile --check-files
- run: yarn typecheck
- name: Run unit tests
working-directory: packages/worker
run: yarn test:unit
@@ -41,7 +94,9 @@ 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
@@ -49,16 +104,18 @@ jobs:
environment: ${{ github.ref == 'refs/heads/main' && 'staging' || '' }}
system-tests-staging:
if: github.ref == 'refs/heads/main' && !failure() && !cancelled()
if: github.ref == 'refs/heads/main' && !failure() && !cancelled() && needs.changes.outputs.run_worker == 'true'
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()
if: github.ref == 'refs/heads/main' && !failure() && !cancelled() && needs.changes.outputs.run_worker == 'true'
needs:
- changes
- system-tests-staging
uses: ./.github/workflows/build-and-deploy-worker.yml
secrets: inherit
@@ -68,8 +125,9 @@ jobs:
notify-slack:
runs-on: ubuntu-latest
needs:
- changes
- deploy-worker-production
if: github.ref == 'refs/heads/main' && always() && !cancelled()
if: github.ref == 'refs/heads/main' && always() && !cancelled() && needs.changes.outputs.run_worker == 'true'
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
-9
View File
@@ -10,23 +10,14 @@ 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))
+9 -12
View File
@@ -106,17 +106,14 @@ yarn start-allow-unused
### Testing
```bash
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
yarn test # Run all tests across packages (Jest multi-project)
yarn test --watch # Run tests in watch mode
# Run tests for a specific package
# Run tests for specific package
cd packages/eas-cli && yarn test
cd packages/eas-json && yarn test
cd packages/worker && yarn test
cd packages/build-tools && yarn test
cd packages/build-tools && yarn jest-unit
cd packages/steps && yarn test
```
@@ -246,8 +243,8 @@ Most packages depend on `@expo/eas-build-job` as the source of truth for types.
## Testing Architecture
- **Framework**: Jest with per-package configuration
- **Shared base config**: `jest/jest.shared.config.ts`
- **Framework**: Jest with multi-project configuration
- **Root config**: `jest.config.ts` (combines eas-cli, eas-json, worker)
- **Test locations**: `__tests__/` directories alongside source files
- **Mocking**:
- File system: `memfs`
@@ -257,9 +254,9 @@ Most packages depend on `@expo/eas-build-job` as the source of truth for types.
Run a single test file:
```bash
cd packages/eas-cli && yarn test <path-to-test-file>
yarn test <path-to-test-file>
# Example:
cd packages/eas-cli && yarn test src/project/__tests__/projectUtils-test.ts
yarn test packages/eas-cli/src/project/__tests__/projectUtils-test.ts
```
## Common Development Scenarios
@@ -354,6 +351,6 @@ See `LICENSE` (MIT) and `LICENSE-BUSL` (BUSL-1.1) for details.
## Important Notes
- **Node Version**: Requires Node.js (managed via Mise)
- **Node Version**: Requires Node.js >= 18.0.0 (managed via Volta)
- **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
From the repository root, run `yarn lerna run test`. For a single package, run `yarn test` in that package directory.
Run `yarn test` either in the repository root or in a package directory that you're working on.
## Releasing
+17
View File
@@ -0,0 +1,17 @@
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
@@ -0,0 +1,9 @@
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
@@ -1,8 +0,0 @@
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": "17.0.0",
"version": "16.32.0",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
-3
View File
@@ -1,3 +0,0 @@
[tools]
node = "20.19.4"
yarn = "1.22.22"
+5 -1
View File
@@ -16,7 +16,7 @@
"lint": "eslint .",
"lint-changelog": "./scripts/bin/run lint-changelog",
"release": "lerna run rebuild && ./scripts/bin/run release",
"test": "lerna run test",
"test": "jest",
"clean": "lerna run clean && rm -rf node_modules coverage"
},
"devDependencies": {
@@ -33,5 +33,9 @@
"prettier": "3.1.1",
"ts-jest": "29.1.1",
"typescript": "5.5.4"
},
"volta": {
"node": "20.19.4",
"yarn": "1.22.21"
}
}
@@ -1,10 +1,8 @@
import type { Config } from '@jest/types';
import type { Config } from 'jest';
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../../jest/jest.shared.config';
const config: Config.InitialOptions = {
...baseConfig,
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: '../src',
testMatch: ['**/__integration-tests__/*.test.ts'],
clearMocks: true,
+4 -6
View File
@@ -1,10 +1,8 @@
import type { Config } from '@jest/types';
import type { Config } from 'jest';
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../../jest/jest.shared.config';
const config: Config.InitialOptions = {
...baseConfig,
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: '../src',
testMatch: [
'**/__tests__/*.test.ts',
+12 -10
View File
@@ -5,7 +5,7 @@
"url": "https://github.com/expo/eas-cli.git",
"directory": "packages/build-tools"
},
"version": "17.0.0",
"version": "1.0.272",
"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": "17.0.0",
"@expo/eas-build-job": "17.0.0",
"@expo/downloader": "1.0.260",
"@expo/eas-build-job": "1.0.265",
"@expo/env": "^0.4.0",
"@expo/logger": "17.0.0",
"@expo/logger": "1.0.260",
"@expo/package-manager": "1.9.10",
"@expo/plist": "^0.2.0",
"@expo/results": "^1.0.0",
"@expo/spawn-async": "1.7.2",
"@expo/steps": "17.0.0",
"@expo/template-file": "17.0.0",
"@expo/turtle-spawn": "17.0.0",
"@expo/steps": "1.0.271",
"@expo/template-file": "1.0.260",
"@expo/turtle-spawn": "1.0.260",
"@expo/xcpretty": "^4.3.1",
"@google-cloud/storage": "^7.11.2",
"@urql/core": "^6.0.1",
@@ -55,7 +55,6 @@
"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",
@@ -66,7 +65,7 @@
"resolve-from": "^5.0.0",
"retry": "^0.13.1",
"semver": "^7.6.2",
"tar": "7.5.7",
"tar": "^7.4.3",
"uuid": "9.0.1",
"yaml": "^2.8.1",
"zod": "^4.3.5"
@@ -87,11 +86,14 @@
"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,7 +36,6 @@ 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';
@@ -76,7 +75,6 @@ export function getEasFunctions(ctx: CustomBuildContext): BuildFunction[] {
calculateEASUpdateRuntimeVersionFunction(),
createSubmissionEntityFunction(),
createUploadToAscBuildFunction(),
createInternalEasMaestroTestFunction(ctx),
];
@@ -57,12 +57,6 @@ 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({
@@ -99,13 +93,8 @@ export function createRepackBuildFunction(): BuildFunction {
}
: undefined;
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,
});
stepsCtx.logger.info(`Using repack tool version: ${inputs.repack_version.value}`);
const repackApp = await installAndImportRepackAsync(inputs.repack_version.value as string);
const { repackAppIosAsync, repackAppAndroidAsync } = repackApp;
stepsCtx.logger.info('Repacking the app...');
@@ -171,19 +160,15 @@ export function createRepackBuildFunction(): BuildFunction {
/**
* Install `@expo/repack-app` in a sandbox directory and import it.
*/
async function installAndImportRepackAsync({
packageName,
version,
}: {
packageName: string;
version: string;
}): Promise<typeof import('@expo/repack-app')> {
async function installAndImportRepackAsync(
version: string = 'latest'
): Promise<typeof import('@expo/repack-app')> {
const sandbox = await fs.promises.mkdtemp(path.join(os.tmpdir(), `repack-package-root-`));
await spawnAsync('yarn', ['add', `${packageName}@${version}`], {
await spawnAsync('yarn', ['add', `@expo/repack-app@${version}`], {
stdio: 'inherit',
cwd: sandbox,
});
return require(resolveFrom(sandbox, packageName));
return require(resolveFrom(sandbox, '@expo/repack-app'));
}
/**
@@ -132,34 +132,7 @@ export function createStartCuttlefishDeviceBuildFunction(): BuildFunction {
logger.info('Listing adb devices...');
await spawn('adb', ['devices'], { 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.');
}
await spawn('adb', ['shell', 'input', 'keyevent', '82'], { env, logger });
},
});
}
@@ -1,330 +0,0 @@
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,10 +1,4 @@
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,
module.exports = {
preset: 'ts-jest/presets/default-esm',
transform: {
'^.+\\.tsx?$': [
@@ -15,6 +9,7 @@ const config: Config.InitialOptions = {
},
],
},
testEnvironment: 'node',
rootDir: 'src',
testMatch: ['**/__tests__/*-test.ts'],
coverageReporters: ['json', 'lcov'],
@@ -26,5 +21,3 @@ const config: Config.InitialOptions = {
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": "17.0.0",
"version": "1.0.271",
"bin": {
"create-eas-build-function": "./build/index.js"
},
@@ -34,7 +34,7 @@
"clean": "rimraf ./build/ *.tsbuildinfo"
},
"dependencies": {
"@expo/steps": "17.0.0"
"@expo/steps": "1.0.271"
},
"devDependencies": {
"@expo/package-manager": "1.9.10",
@@ -53,5 +53,9 @@
},
"publishConfig": {
"access": "public"
},
"volta": {
"node": "20.14.0",
"yarn": "1.22.21"
}
}
+8
View File
@@ -0,0 +1,8 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: 'src',
testMatch: ['**/__tests__/*.test.ts'],
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
-14
View File
@@ -1,14 +0,0 @@
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;
+7 -3
View File
@@ -5,7 +5,7 @@
"url": "https://github.com/expo/eas-cli.git",
"directory": "packages/downloader"
},
"version": "17.0.0",
"version": "1.0.260",
"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.ts",
"test:watch": "jest --config jest.config.ts --watch",
"test": "jest --config jest.config.js",
"test:watch": "jest --config jest.config.js --watch",
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
},
"author": "Expo <[email protected]>",
@@ -35,5 +35,9 @@
"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": "17.0.0",
"version": "16.30.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 --passWithNoTests",
"test": "jest",
"clean": "rimraf build node_modules yarn-error.log *.tsbuildinfo"
},
"files": [
+8
View File
@@ -0,0 +1,8 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: 'src',
testMatch: ['**/__tests__/*.test.ts'],
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
-14
View File
@@ -1,14 +0,0 @@
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;
+8 -4
View File
@@ -5,7 +5,7 @@
"url": "https://github.com/expo/eas-cli.git",
"directory": "packages/eas-build-job"
},
"version": "17.0.0",
"version": "1.0.265",
"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.ts",
"test:watch": "jest --config jest.config.ts --watch",
"test": "jest --config jest.config.js",
"test:watch": "jest --config jest.config.js --watch",
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
},
"author": "Expo <[email protected]>",
@@ -32,9 +32,13 @@
"typescript": "^5.5.4"
},
"dependencies": {
"@expo/logger": "17.0.0",
"@expo/logger": "1.0.260",
"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/v17.0.0/packages/eas-cli/src/commands/account/login.ts)_
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/account/logout.ts)_
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/account/usage.ts)_
_See code: [packages/eas-cli/src/commands/account/usage.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/account/view.ts)_
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/analytics.ts)_
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/branch/create.ts)_
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/branch/delete.ts)_
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/branch/list.ts)_
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/branch/rename.ts)_
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/branch/view.ts)_
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/index.ts)_
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/cancel.ts)_
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/configure.ts)_
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/delete.ts)_
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/dev.ts)_
_See code: [packages/eas-cli/src/commands/build/dev.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/download.ts)_
_See code: [packages/eas-cli/src/commands/build/download.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/inspect.ts)_
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/list.ts)_
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/resign.ts)_
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/run.ts)_
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/version/get.ts)_
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/version/set.ts)_
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/version/sync.ts)_
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/build/view.ts)_
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/channel/create.ts)_
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/channel/delete.ts)_
_See code: [packages/eas-cli/src/commands/channel/delete.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/channel/edit.ts)_
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/channel/list.ts)_
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/channel/pause.ts)_
_See code: [packages/eas-cli/src/commands/channel/pause.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/channel/resume.ts)_
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/channel/rollout.ts)_
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/channel/view.ts)_
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/config.ts)_
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/credentials/index.ts)_
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/deploy/index.ts)_
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/deploy/alias/index.ts)_
_See code: [packages/eas-cli/src/commands/deploy/alias/index.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/deploy/alias/delete.ts)_
_See code: [packages/eas-cli/src/commands/deploy/alias/delete.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/deploy/delete.ts)_
_See code: [packages/eas-cli/src/commands/deploy/delete.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/device/create.ts)_
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/device/delete.ts)_
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/device/list.ts)_
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/device/rename.ts)_
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/device/view.ts)_
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/diagnostics.ts)_
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/env/create.ts)_
_See code: [packages/eas-cli/src/commands/env/create.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/env/delete.ts)_
_See code: [packages/eas-cli/src/commands/env/delete.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/env/exec.ts)_
_See code: [packages/eas-cli/src/commands/env/exec.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/env/get.ts)_
_See code: [packages/eas-cli/src/commands/env/get.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/env/list.ts)_
_See code: [packages/eas-cli/src/commands/env/list.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/env/pull.ts)_
_See code: [packages/eas-cli/src/commands/env/pull.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/env/push.ts)_
_See code: [packages/eas-cli/src/commands/env/push.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/env/update.ts)_
_See code: [packages/eas-cli/src/commands/env/update.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/fingerprint/compare.ts)_
_See code: [packages/eas-cli/src/commands/fingerprint/compare.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/fingerprint/generate.ts)_
_See code: [packages/eas-cli/src/commands/fingerprint/generate.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/metadata/lint.ts)_
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/metadata/pull.ts)_
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/metadata/push.ts)_
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/open.ts)_
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/project/info.ts)_
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/project/init.ts)_
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/project/new.ts)_
_See code: [packages/eas-cli/src/commands/project/new.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/project/onboarding.ts)_
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/submit.ts)_
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/index.ts)_
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/configure.ts)_
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/delete.ts)_
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/edit.ts)_
_See code: [packages/eas-cli/src/commands/update/edit.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/list.ts)_
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/republish.ts)_
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/revert-update-rollout.ts)_
_See code: [packages/eas-cli/src/commands/update/revert-update-rollout.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/rollback.ts)_
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/update/view.ts)_
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/upload.ts)_
_See code: [packages/eas-cli/src/commands/upload.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/webhook/create.ts)_
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/webhook/delete.ts)_
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/webhook/list.ts)_
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/webhook/update.ts)_
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/webhook/view.ts)_
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/workflow/cancel.ts)_
_See code: [packages/eas-cli/src/commands/workflow/cancel.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/workflow/create.ts)_
_See code: [packages/eas-cli/src/commands/workflow/create.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/workflow/logs.ts)_
_See code: [packages/eas-cli/src/commands/workflow/logs.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/workflow/run.ts)_
_See code: [packages/eas-cli/src/commands/workflow/run.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/workflow/runs.ts)_
_See code: [packages/eas-cli/src/commands/workflow/runs.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/workflow/status.ts)_
_See code: [packages/eas-cli/src/commands/workflow/status.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/workflow/validate.ts)_
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v16.32.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/v17.0.0/packages/eas-cli/src/commands/workflow/view.ts)_
_See code: [packages/eas-cli/src/commands/workflow/view.ts](https://github.com/expo/eas-cli/blob/v16.32.0/packages/eas-cli/src/commands/workflow/view.ts)_
<!-- commandsstop -->
+3 -4
View File
@@ -1,13 +1,12 @@
import type { Config } from '@jest/types';
import type { Config } from 'jest';
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../jest/jest.shared.config';
import baseConfig from '../../jest/jest.config';
const config: Config.InitialOptions = {
const config: Config = {
...baseConfig,
rootDir: __dirname,
roots: ['src', '__mocks__'],
testRegex: '/__tests__/.*(test|spec)\\.[jt]sx?$',
globalSetup: './jest/global-setup.ts',
setupFilesAfterEnv: ['./jest/setup-after-env.ts'],
};
+8 -6
View File
@@ -1,7 +1,7 @@
{
"name": "eas-cli",
"description": "EAS command line tool",
"version": "17.0.0",
"version": "16.32.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": "17.0.0",
"@expo/eas-json": "17.0.0",
"@expo/eas-build-job": "1.0.265",
"@expo/eas-json": "16.30.0",
"@expo/env": "^1.0.0",
"@expo/json-file": "8.3.3",
"@expo/logger": "17.0.0",
"@expo/logger": "1.0.260",
"@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": "17.0.0",
"@expo/steps": "1.0.271",
"@expo/timeago.js": "1.0.0",
"@oclif/core": "^1.26.2",
"@oclif/plugin-autocomplete": "^2.3.10",
@@ -61,6 +61,7 @@
"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",
@@ -82,7 +83,7 @@
"semver": "7.5.4",
"set-interval-async": "3.0.3",
"slash": "3.0.0",
"tar": "7.5.7",
"tar": "6.2.1",
"tar-stream": "3.1.7",
"terminal-link": "2.1.1",
"ts-deepmerge": "6.2.0",
@@ -108,6 +109,7 @@
"@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",
+5 -54
View File
@@ -5,57 +5,8 @@ set -eo pipefail
EAS_CLI_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
PACKAGE_JSON_PATH="$EAS_CLI_DIR/package.json"
# Define local packages and their directory names
# Only include packages that both exist in packages/ and are dependencies of the packages we care about
# We start with direct dependencies of eas-cli and their own local dependencies
PACKAGES=(
"@expo/eas-json:eas-json"
"@expo/eas-build-job:eas-build-job"
"@expo/logger:logger"
"@expo/steps:steps"
)
# Function to rewrite dependencies in a package.json file
rewrite_deps() {
local PKG_JSON=$1
local IS_EAS_CLI=$2
for package in "${PACKAGES[@]}" ; do
KEY="${package%%:*}"
DIR="${package##*:}"
# Determine the relative path. If IS_EAS_CLI we go one level deeper because oclif builds in `tmp` in current directory.
if [[ "$IS_EAS_CLI" == "true" ]]; then
REL_PATH="file:../../../$DIR"
else
REL_PATH="file:../$DIR"
fi
# Replace version with relative path
# Using # delimiter for sed to handle slashes
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "s#\"$KEY\": \".*\"#\"$KEY\": \"$REL_PATH\"#g" "$PKG_JSON"
else
sed -i "s#\"$KEY\": \".*\"#\"$KEY\": \"$REL_PATH\"#g" "$PKG_JSON"
fi
done
}
# Update eas-cli/package.json
echo "Updating eas-cli/package.json"
rewrite_deps "$PACKAGE_JSON_PATH" "true"
# Update other relevant local packages
# We iterate the PACKAGES list itself to find their locations and update them
# This ensures we only touch packages that are part of the dependency graph we care about
WORKSPACE_ROOT="$(cd "$EAS_CLI_DIR/.." && pwd)"
for package in "${PACKAGES[@]}" ; do
DIR="${package##*:}"
PKG_JSON="$WORKSPACE_ROOT/$DIR/package.json"
if [[ -f "$PKG_JSON" ]]; then
echo "Updating $DIR/package.json"
rewrite_deps "$PKG_JSON" "false"
fi
done
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
+1 -13
View File
@@ -40,19 +40,7 @@ export async function selectBranchOnAppAsync(
}
): Promise<UpdateBranchFragment> {
if (paginatedQueryOptions.nonInteractive) {
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}`
);
throw new Error('Unable to select a branch in non-interactive mode.');
}
const selectedBranch = await paginatedQueryWithSelectPromptAsync({
+1 -20
View File
@@ -77,26 +77,7 @@ export async function listAndSelectBuildOnAppAsync(
}
): Promise<BuildFragment | null> {
if (paginatedQueryOptions.nonInteractive) {
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}`
);
throw new Error('Unable to select a build in non-interactive mode.');
} 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({ nonInteractive: flags.nonInteractive });
await vcsClient.ensureRepoExistsAsync();
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 * as tar from 'tar';
import tar from 'tar';
import { v4 as uuidv4 } from 'uuid';
import Log, { learnMore } from '../../log';
+1 -12
View File
@@ -43,18 +43,7 @@ export async function selectChannelOnAppAsync(
}
): Promise<UpdateChannelObject> {
if (paginatedQueryOptions.nonInteractive) {
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}`
);
throw new Error('Unable to select a channel in non-interactive mode.');
}
const updateChannel = await paginatedQueryWithSelectPromptAsync({
@@ -17,11 +17,6 @@ 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 = {
@@ -31,7 +26,7 @@ export default class AccountLogin extends EasCommand {
async runAsync(): Promise<void> {
const {
flags: { sso, browser },
flags: { sso },
} = await this.parse(AccountLogin);
const {
@@ -56,7 +51,7 @@ export default class AccountLogin extends EasCommand {
}
}
await sessionManager.showLoginPromptAsync({ sso, browser });
await sessionManager.showLoginPromptAsync({ sso });
Log.log('Logged in');
}
}
@@ -46,7 +46,9 @@ 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.'
);
await vcsClient.ensureRepoExistsAsync({ nonInteractive: false });
// BuildConfigure.ContextOptions.Vcs.client.getValueAsync()
await vcsClient.ensureRepoExistsAsync();
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({ nonInteractive: false });
await ensureRepoIsCleanAsync(vcsClient, false);
await vcsClient.ensureRepoExistsAsync();
await ensureRepoIsCleanAsync(vcsClient, flags.nonInteractive);
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({ nonInteractive: false });
await vcsClient.ensureRepoExistsAsync();
await vcsClient.makeShallowCopyAsync(tmpWorkingdir);
await this.copyToOutputDirAsync(tmpWorkingdir, outputDirectory);
} else {
@@ -0,0 +1,287 @@
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({ nonInteractive: flags['non-interactive'] });
await vcsClient.ensureRepoExistsAsync();
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
const easJsonCliConfig: EasJson['cli'] =
@@ -240,7 +240,7 @@ export default class UpdatePublish extends EasCommand {
enableJsonOutput();
}
await vcsClient.ensureRepoExistsAsync({ nonInteractive });
await vcsClient.ensureRepoExistsAsync();
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 * as tar from 'tar';
import tar from 'tar';
import { v4 as uuidv4 } from 'uuid';
import { getBuildLogsUrl } from '../build/utils/url';
+2 -31
View File
@@ -31,22 +31,7 @@ export async function selectAppleTeamOnAccountAsync(
}
): Promise<AppleTeamFragment> {
if (paginatedQueryOptions.nonInteractive) {
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}`
);
throw new Error('Unable to select an Apple team in non-interactive mode.');
} else {
const selectedAppleTeam = await paginatedQueryWithSelectPromptAsync({
limit: paginatedQueryOptions.limit ?? TEAMS_LIMIT,
@@ -89,21 +74,7 @@ export async function selectAppleDeviceOnAppleTeamAsync(
}
): Promise<AppleDeviceFragment> {
if (paginatedQueryOptions.nonInteractive) {
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}`
);
throw new Error('Unable to select an Apple device in non-interactive mode.');
} else {
const selectedAppleDevice = await paginatedQueryWithSelectPromptAsync({
limit: paginatedQueryOptions.limit ?? DEVICES_LIMIT,
@@ -41,13 +41,7 @@ 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. Available actions: ${[
ManageRolloutActions.EDIT,
ManageRolloutActions.END,
ManageRolloutActions.VIEW,
].join(', ')}.`
);
throw new Error(`rollout selection cannot be run in non-interactive mode.`);
}
const channelObject = await this.getChannelObjectAsync(ctx);
printRollout(channelObject);
@@ -52,20 +52,7 @@ export class SelectRuntime implements EASUpdateAction<string | null> {
const { nonInteractive, graphqlClient, app } = ctx;
const { projectId } = app;
if (nonInteractive) {
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}`
);
throw new NonInteractiveError(`runtime selection cannot be run in non-interactive mode.`);
}
const newestRuntimeConnection = await this.getNewestRuntimeAsync(graphqlClient, {
@@ -1,8 +1,9 @@
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>>;
@@ -239,11 +240,9 @@ export type AscApiClientPatchApi = {
export class AscApiClient {
private readonly baseUrl = 'https://api.appstoreconnect.apple.com';
private readonly token: string;
private readonly logger?: bunyan;
constructor({ token, logger }: { token: string; logger?: bunyan }) {
constructor({ token }: { token: string }) {
this.token = token;
this.logger = logger;
}
public async getAsync<TPath extends keyof typeof GetApi>(
@@ -258,18 +257,9 @@ 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}?${searchParams.toString()}`,
path: `${effectivePath}?${new URLSearchParams(body).toString()}`,
body,
requestSchema: schema.request,
responseSchema: schema.response,
@@ -353,7 +343,7 @@ export class AscApiClient {
const json = await response.json();
this.logger?.debug(`Response from App Store Connect: ${JSON.stringify(json, null, 2)}`);
Log.debug(`Response from App Store Connect: ${JSON.stringify(json, null, 2)}`);
const parsedResponse = await asyncResult((async () => responseSchema.parse(json))());
if (!parsedResponse.ok) {
@@ -2,9 +2,6 @@ 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 });
@@ -57,7 +57,7 @@ describe('update queries', () => {
limit: 50,
},
})
).rejects.toThrowError(`No update groups found on branch "${branchName}".`);
).rejects.toThrowError(`Unable to select an update in non-interactive mode.`);
});
});
});
+2 -32
View File
@@ -126,18 +126,7 @@ export async function selectRuntimeAndGetLatestUpdateGroupForEachPublishPlatform
}
): Promise<Record<UpdatePublishPlatform, UpdateFragment[] | undefined>> {
if (paginatedQueryOptions.nonInteractive) {
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}`
);
throw new Error('Unable to select an update in non-interactive mode.');
}
const runtimeVersion = await selectRuntimeOnBranchAsync(graphqlClient, {
@@ -189,26 +178,7 @@ export async function selectUpdateGroupOnBranchAsync(
}
): Promise<UpdateFragment[]> {
if (paginatedQueryOptions.nonInteractive) {
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}`
);
throw new Error('Unable to select an update in non-interactive mode.');
}
const updateGroup = await paginatedQueryWithSelectPromptAsync({
+5 -8
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,7 +149,6 @@ export default class SessionManager {
nonInteractive = false,
printNewLine = false,
sso = false,
browser = false,
} = {}): Promise<void> {
if (nonInteractive) {
Errors.error(
@@ -165,8 +164,8 @@ export default class SessionManager {
Log.newLine();
}
if (sso || browser) {
await this.browserLoginAsync({ sso });
if (sso) {
await this.ssoLoginAsync();
return;
}
@@ -206,10 +205,8 @@ export default class SessionManager {
}
}
private async browserLoginAsync({ sso = false }): Promise<void> {
const { sessionSecret, id, username } = await fetchSessionSecretAndUserFromBrowserAuthFlowAsync(
{ sso }
);
private async ssoLoginAsync(): Promise<void> {
const { sessionSecret, id, username } = await fetchSessionSecretAndSsoUserAsync();
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('../fetchSessionSecretAndUserFromBrowserAuthFlow');
jest.mock('../fetchSessionSecretAndSsoUser');
jest.mock('../../api');
const authStub: any = {
@@ -156,15 +156,15 @@ describe(SessionManager, () => {
});
});
describe('browserLoginAsync', () => {
it('saves user data to ~/.expo/state.json with sso', async () => {
jest.mocked(fetchSessionSecretAndUserFromBrowserAuthFlowAsync).mockResolvedValue({
describe('ssoLoginAsync', () => {
it('saves user data to ~/.expo/state.json', async () => {
jest.mocked(fetchSessionSecretAndSsoUserAsync).mockResolvedValue({
sessionSecret: 'SESSION_SECRET',
id: 'USER_ID',
username: 'USERNAME',
});
const sessionManager = new SessionManager(analytics);
await sessionManager['browserLoginAsync']({ sso: true });
await sessionManager['ssoLoginAsync']();
expect(await fs.readFile(getStateJsonPath(), 'utf8')).toMatchInlineSnapshot(`
"{
"auth": {
@@ -176,31 +176,6 @@ 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,
});
});
});
@@ -317,18 +292,7 @@ describe(SessionManager, () => {
// SSO login
await sessionManager.showLoginPromptAsync({ sso: true });
expect(promptAsync).not.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,
});
expect(fetchSessionSecretAndSsoUserAsync).toHaveBeenCalled();
});
});
@@ -4,22 +4,53 @@ import http from 'http';
import { Socket } from 'node:net';
import querystring from 'querystring';
import { getExpoWebsiteBaseUrl } from '../api';
import Log from '../log';
export async function getSessionUsingBrowserAuthFlowAsync({ sso = false }): Promise<string> {
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> {
const scheme = 'http';
const hostname = 'localhost';
const path = '/auth/callback';
const expoWebsiteUrl = getExpoWebsiteBaseUrl();
const buildExpoLoginUrl = (port: number, sso: boolean): string => {
const params = querystring.stringify({
confirm_account: true,
const buildExpoSsoLoginUrl = (port: number): string => {
const data = {
app_redirect_uri: `${scheme}://${hostname}:${port}${path}`,
});
return `${expoWebsiteUrl}${sso ? '/sso-login' : '/login'}?${params}`;
};
const params = querystring.stringify(data);
return `${expoWebsiteUrl}/sso-login?${params}`;
};
// Start server and begin auth flow
@@ -29,19 +60,9 @@ export async function getSessionUsingBrowserAuthFlowAsync({ sso = false }): Prom
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 login response.');
throw new Error('Unexpected SSO login response.');
}
const url = new URL(request.url, `http:${request.headers.host}`);
const sessionSecret = url.searchParams.get('session_secret');
@@ -50,10 +71,17 @@ export async function getSessionUsingBrowserAuthFlowAsync({ sso = false }): Prom
throw new Error('Request missing session_secret search parameter.');
}
resolve(sessionSecret);
redirectAndCleanup('success');
response.writeHead(200, { 'Content-Type': 'text/html' });
response.write(successBody);
response.end();
} catch (error) {
redirectAndCleanup('error');
reject(error);
} finally {
server.close();
// Ensure that the server shuts down
for (const connection of connections) {
connection.destroy();
}
}
}
);
@@ -67,7 +95,7 @@ export async function getSessionUsingBrowserAuthFlowAsync({ sso = false }): Prom
'Server address and port should be set after listening has begun'
);
const port = address.port;
const authorizeUrl = buildExpoLoginUrl(port, sso);
const authorizeUrl = buildExpoSsoLoginUrl(port);
Log.log(
`If your browser doesn't automatically open, visit this link to log in: ${authorizeUrl}`
);
@@ -1,12 +1,15 @@
import { getSessionUsingBrowserAuthFlowAsync } from './expoBrowserAuthFlowLauncher';
import { getSessionUsingBrowserAuthFlowAsync } from './expoSsoLauncher';
import { fetchUserAsync } from './fetchUser';
import { getExpoWebsiteBaseUrl } from '../api';
export async function fetchSessionSecretAndUserFromBrowserAuthFlowAsync({ sso = false }): Promise<{
export async function fetchSessionSecretAndSsoUserAsync(): Promise<{
sessionSecret: string;
id: string;
username: string;
}> {
const sessionSecret = await getSessionUsingBrowserAuthFlowAsync({ sso });
const sessionSecret = await getSessionUsingBrowserAuthFlowAsync({
expoWebsiteUrl: getExpoWebsiteBaseUrl(),
});
const userData = await fetchUserAsync({ sessionSecret });
+3 -7
View File
@@ -31,9 +31,7 @@ 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. Valid values: string, file.'
);
throw new Error('The `--type` flag must be set when running in `--non-interactive` mode.');
}
const options = [
@@ -91,7 +89,7 @@ export async function promptVariableVisibilityAsync(
): Promise<EnvironmentVariableVisibility> {
if (nonInteractive) {
throw new Error(
'The `--visibility` flag must be set when running in `--non-interactive` mode. Valid values: plaintext, sensitive, secret.'
'The `--visibility` flag must be set when running in `--non-interactive` mode.'
);
}
return await selectAsync('Select visibility:', [
@@ -138,9 +136,7 @@ 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. Default environments: ${DEFAULT_ENVIRONMENTS.join(
', '
)}.`
'The `--environment` flag must be set when running in `--non-interactive` mode.'
);
}
@@ -255,27 +255,4 @@ 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,12 +61,10 @@ export function displayOverageWarning({
hasFreePlan: boolean;
name: string;
}): void {
const message = chalk.bold(
`You've used ${percentUsed}% of your included build credits for this month.`
Log.warn(
chalk.bold(`You've used ${percentUsed}% of your included build credits for this month. `) +
createProgressBar(percentUsed)
);
// 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
+11 -36
View File
@@ -31,11 +31,7 @@ export default class GitClient extends Client {
this.requireCommit = options.requireCommit;
}
public override async ensureRepoExistsAsync(options?: {
nonInteractive?: boolean;
}): Promise<void> {
const nonInteractive = options?.nonInteractive ?? false;
public override async ensureRepoExistsAsync(): Promise<void> {
try {
if (!(await isGitInstalledAsync())) {
Log.error(
@@ -70,26 +66,11 @@ 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
@@ -438,22 +419,16 @@ export default class GitClient extends Client {
return;
}
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]"',
Log.warn(
`You need to configure Git with your ${[
!usernameConfigured && 'username (user.name)',
!emailConfigured && 'email address (user.email)',
]
.filter(i => i)
.join(' && ');
throw new Error(
`Git ${missingConfig.join(' and ')} must be configured. Run: ${configCommands}`
);
.join(' and ')}`
);
if (nonInteractive) {
throw new Error('Git cannot be configured automatically in non-interactive mode');
}
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(_options?: { nonInteractive?: boolean }): Promise<void> {}
public async ensureRepoExistsAsync(): Promise<void> {}
// (optional) checks whether commit is necessary before calling makeShallowCopyAsync
//
+3 -4
View File
@@ -1,13 +1,12 @@
import type { Config } from '@jest/types';
import type { Config } from 'jest';
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../jest/jest.shared.config';
import baseConfig from '../../jest/jest.config';
const config: Config.InitialOptions = {
const config: Config = {
...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": "17.0.0",
"version": "16.30.0",
"author": "Expo <[email protected]>",
"bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": {
"@babel/code-frame": "7.23.5",
"@expo/eas-build-job": "17.0.0",
"@expo/eas-build-job": "1.0.265",
"chalk": "4.1.2",
"env-string": "1.0.1",
"fs-extra": "11.2.0",
+10 -7
View File
@@ -5,7 +5,7 @@
"url": "https://github.com/expo/eas-cli.git",
"directory": "packages/local-build-plugin"
},
"version": "17.0.0",
"version": "1.0.272",
"description": "Tool for running EAS compatible builds on a local machine.",
"main": "dist/main.js",
"files": [
@@ -21,7 +21,6 @@
"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": {
@@ -31,11 +30,11 @@
"bugs": "https://github.com/expo/eas-cli/issues",
"license": "BUSL-1.1",
"dependencies": {
"@expo/build-tools": "17.0.0",
"@expo/eas-build-job": "17.0.0",
"@expo/logger": "17.0.0",
"@expo/build-tools": "1.0.272",
"@expo/eas-build-job": "1.0.265",
"@expo/logger": "1.0.260",
"@expo/spawn-async": "^1.7.2",
"@expo/turtle-spawn": "17.0.0",
"@expo/turtle-spawn": "1.0.260",
"bunyan": "^1.8.15",
"chalk": "4.1.2",
"env-paths": "^2.2.1",
@@ -44,7 +43,7 @@
"lodash": "^4.17.21",
"nullthrows": "^1.1.1",
"semver": "^7.6.2",
"tar": "7.5.7",
"tar": "^7.2.0",
"uuid": "^9.0.1"
},
"devDependencies": {
@@ -57,5 +56,9 @@
"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
@@ -0,0 +1,8 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: 'src',
testMatch: ['**/__tests__/*.test.ts'],
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
-14
View File
@@ -1,14 +0,0 @@
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;
+7 -3
View File
@@ -5,7 +5,7 @@
"url": "https://github.com/expo/eas-cli.git",
"directory": "packages/logger"
},
"version": "17.0.0",
"version": "1.0.260",
"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.ts --passWithNoTests",
"test:watch": "jest --config jest.config.ts --watch",
"test": "jest --config jest.config.js --passWithNoTests",
"test:watch": "jest --config jest.config.js --watch",
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
},
"author": "Expo <[email protected]>",
@@ -34,5 +34,9 @@
"jest": "^29.7.0",
"ts-jest": "^29.1.4",
"typescript": "^5.5.4"
},
"volta": {
"node": "20.14.0",
"yarn": "1.22.21"
}
}
@@ -1,10 +1,6 @@
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,
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: 'src',
testMatch: ['**/__tests__/*-test.ts'],
coverageReporters: ['json', 'lcov'],
@@ -13,5 +9,3 @@ const config: Config.InitialOptions = {
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
export default config;
+9 -5
View File
@@ -5,7 +5,7 @@
"url": "https://github.com/expo/eas-cli.git",
"directory": "packages/steps"
},
"version": "17.0.0",
"version": "1.0.271",
"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.ts",
"test:coverage": "jest --config jest.config.ts --coverage",
"test": "jest --config jest.config.cjs",
"test:coverage": "jest --config jest.config.cjs --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": "17.0.0",
"@expo/logger": "17.0.0",
"@expo/eas-build-job": "1.0.265",
"@expo/logger": "1.0.260",
"@expo/spawn-async": "^1.7.2",
"arg": "^5.0.2",
"fs-extra": "^11.2.0",
@@ -54,5 +54,9 @@
"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
@@ -0,0 +1,8 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: 'src',
testMatch: ['**/__tests__/*.test.ts'],
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
-14
View File
@@ -1,14 +0,0 @@
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;
+7 -3
View File
@@ -5,7 +5,7 @@
"url": "https://github.com/expo/eas-cli.git",
"directory": "packages/template-file"
},
"version": "17.0.0",
"version": "1.0.260",
"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.ts",
"test:watch": "jest --config jest.config.ts --watch",
"test": "jest --config jest.config.js",
"test:watch": "jest --config jest.config.js --watch",
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
},
"author": "Expo <[email protected]>",
@@ -35,5 +35,9 @@
"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
@@ -0,0 +1,8 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: 'src',
testMatch: ['**/__tests__/*.test.ts'],
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/../jest/setup-tests.ts'],
};
-14
View File
@@ -1,14 +0,0 @@
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;
+8 -4
View File
@@ -5,7 +5,7 @@
"url": "https://github.com/expo/eas-cli.git",
"directory": "packages/turtle-spawn"
},
"version": "17.0.0",
"version": "1.0.260",
"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.ts --passWithNoTests",
"test:watch": "jest --config jest.config.ts --watch",
"test": "jest --config jest.config.js --passWithNoTests",
"test:watch": "jest --config jest.config.js --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": "17.0.0",
"@expo/logger": "1.0.260",
"@expo/spawn-async": "^1.7.2"
},
"devDependencies": {
@@ -34,5 +34,9 @@
"jest": "^29.7.0",
"ts-jest": "^29.1.4",
"typescript": "^5.5.4"
},
"volta": {
"node": "20.14.0",
"yarn": "1.22.21"
}
}
@@ -16,8 +16,6 @@ jobs:
steps:
- uses: eas/checkout
- uses: eas/install_node_modules
- name: Build Android worker tarball
run: ./package.sh worker-android.tar.gz android
@@ -35,8 +33,6 @@ 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,3 +5,5 @@ export PORT="3005"
export WORKER_PORT="3005"
export WORKER_IP="localhost"
export LOGGER_LEVEL="debug"
unset _VOLTA_TOOL_RECURSION
@@ -1,10 +1,6 @@
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,
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: 'src',
testMatch: ['**/__integration__/*.test.ts'],
collectCoverage: true,
@@ -17,5 +13,3 @@ const config: Config.InitialOptions = {
// WebSocket cleanup timing) may keep handles open that prevent clean exit.
forceExit: true,
};
export default config;
+5 -9
View File
@@ -1,14 +1,10 @@
import type { Config } from '@jest/types';
import type { Config } from 'jest';
// eslint-disable-next-line import/no-relative-packages
import baseConfig from '../../jest/jest.shared.config';
const config: Config.InitialOptions = {
...baseConfig,
export default {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: __dirname,
testMatch: ['**/__unit__/*.test.ts'],
clearMocks: true,
setupFilesAfterEnv: ['./jest/unit-setup.ts'],
};
export default config;
} satisfies Config;
-4
View File
@@ -9,10 +9,6 @@ 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": "17.0.0",
"version": "16.32.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.ts",
"test:integration:watch": "./resources/jest --runInBand --config jest.config.integration.ts --watchAll",
"test:integration": "./resources/jest --runInBand --config jest.config.integration.js",
"test:integration:watch": "./resources/jest --runInBand --config jest.config.integration.js --watchAll",
"typecheck": "tsc"
},
"engines": {
@@ -22,18 +22,18 @@
},
"dependencies": {
"@babel/core": "7.24.7",
"@expo/build-tools": "17.0.0",
"@expo/build-tools": "1.0.272",
"@expo/config-plugins": "8.0.8",
"@expo/config-types": "52.0.1",
"@expo/downloader": "17.0.0",
"@expo/eas-build-job": "17.0.0",
"@expo/logger": "17.0.0",
"@expo/downloader": "1.0.260",
"@expo/eas-build-job": "1.0.265",
"@expo/logger": "1.0.260",
"@expo/results": "1.0.0",
"@expo/rudder-sdk-node": "1.1.1",
"@expo/spawn-async": "1.7.2",
"@expo/steps": "17.0.0",
"@expo/template-file": "17.0.0",
"@expo/turtle-spawn": "17.0.0",
"@expo/steps": "1.0.271",
"@expo/template-file": "1.0.260",
"@expo/turtle-spawn": "1.0.260",
"@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": "7.5.7",
"tar": "6.2.1",
"uuid": "3.4.0",
"ws": "8.18.0",
"xml2js": "0.6.2",
+5 -16
View File
@@ -33,16 +33,9 @@ copy_package() {
dst=$2
mkdir -p $dst
cp "$src/package.json" "$dst/package.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 "$src/tsconfig.json" "$dst/tsconfig.json"
cp "$src/tsconfig.build.json" "$dst/tsconfig.build.json"
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
@@ -50,16 +43,11 @@ 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 --frozen-lockfile
yarn install --silent
yarn build
yarn install --silent --production=true --frozen-lockfile
yarn install --silent --production=true
rm -rf tsconfig.json tsconfig.build.json
popd >/dev/null 2>&1
@@ -77,6 +65,7 @@ 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 * as tar from 'tar';
import 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,
+97 -115
View File
@@ -3,18 +3,13 @@ import downloadFile from '@expo/downloader';
import { Cache, Platform } from '@expo/eas-build-job';
import { randomUUID } from 'crypto';
import fs from 'fs-extra';
import os from 'os';
import path from 'path';
import { vol } from 'memfs';
import { Readable } from 'stream';
// 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');
import { GCSCacheManager } from '../CacheManager';
jest.mock('fs');
jest.mock('fs/promises');
jest.mock('@expo/downloader', () => {
return jest.fn();
});
@@ -28,67 +23,59 @@ jest.mock('../sentry', () => ({
handleError: jest.fn(),
}));
const { GCSCacheManager } = require('../CacheManager');
describe(GCSCacheManager, () => {
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-'));
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();
});
afterEach(async () => {
await fs.rm(tmpDir, { recursive: true, force: true });
await fs.rm(outsideDir, { recursive: true, force: true });
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);
}
describe(GCSCacheManager, () => {
beforeEach(() => {
vol.reset();
});
afterEach(() => {
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(
() => {
return fs.outputFile(path.join(tmpDir, 'build', 'index.ts'), 'index.ts');
vol.fromJSON({
'/projectRoot/build/index.ts': 'index.ts',
});
},
() => {},
{
@@ -96,18 +83,18 @@ describe(GCSCacheManager, () => {
}
);
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);
expect(vol.toJSON()).toStrictEqual({
'/projectRoot/build/index.ts': 'index.ts',
'/projectRoot/cache-restore.tar.gz': expect.anything(),
});
});
test('save and restore for a single directory', async () => {
await saveAndRestoreCacheAsync(
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'),
]);
() => {
vol.fromJSON({
'/projectRoot/build/src/index.ts': 'index.ts',
'/projectRoot/build/src/main.ts': 'index.ts',
});
},
() => {},
{
@@ -115,78 +102,73 @@ describe(GCSCacheManager, () => {
}
);
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);
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(),
});
});
test('save and restore for a single directory outside of the project structure', async () => {
await saveAndRestoreCacheAsync(
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'),
]);
() => {
vol.fromJSON({
'/projectRoot/build/src/index.ts': 'index.ts',
'/outsideProjectRoot/build/src/index.ts': 'index.ts',
});
},
() => {},
{
paths: [path.join(outsideDir, 'build')],
paths: ['/outsideProjectRoot/build'],
}
);
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);
expect(vol.toJSON()).toStrictEqual({
'/outsideProjectRoot/build/src/index.ts': 'index.ts',
'/projectRoot/cache-restore.tar.gz': expect.anything(),
});
});
test('that save and restore does not override existing files', async () => {
await saveAndRestoreCacheAsync(
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': 'index.ts',
'/projectRoot/build/src/main': 'index.ts',
});
},
() => {
return fs.outputFile(path.join(tmpDir, 'build', 'src', 'index.ts'), 'index2.ts');
vol.fromJSON({
'/projectRoot/build/src/index.ts': 'index2.ts',
});
},
{
paths: ['src'],
}
);
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);
expect(vol.toJSON()).toStrictEqual({
'/projectRoot/build/src/index.ts': 'index2.ts',
'/projectRoot/build/src/main': 'index.ts',
'/projectRoot/cache-restore.tar.gz': expect.anything(),
});
});
test('save and restore for multiple locations', async () => {
await saveAndRestoreCacheAsync(
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'),
]);
() => {
vol.fromJSON({
'/projectRoot/build/src/index.ts': 'index.ts',
'/outsideProjectRoot/build/src/index.ts': 'index.ts',
});
},
() => {},
{
paths: ['src', path.join(outsideDir, 'build')],
paths: ['src', '/outsideProjectRoot/build'],
}
);
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);
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(),
});
});
});
+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 * as tar from 'tar';
import tar from 'tar';
import { z } from 'zod';
import config from './config';
Generated
+36 -18
View File
@@ -4843,6 +4843,13 @@
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"
@@ -6316,7 +6323,7 @@ [email protected]:
dependencies:
node-int64 "^0.4.0"
buffer-equal-constant-time@^1.0.1:
[email protected], 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==
@@ -11498,11 +11505,6 @@ 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"
@@ -11688,6 +11690,16 @@ 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"
@@ -11716,6 +11728,15 @@ 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"
@@ -11725,6 +11746,14 @@ 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"
@@ -16296,18 +16325,7 @@ [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.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:
tar@^7.2.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==