Compare commits

...
Author SHA1 Message Date
Expo CI df8ebc8f84 v18.0.6 2026-02-27 22:34:25 +00:00
Tomek Czajecki 62ab329f39 [eas-cli] Fix invisible credential prompts in eas go command (#3429) 2026-02-25 19:22:47 +02:00
Ash Wu 198b796804 [build-tools] Wrap parseMaestroResults in try-catch to prevent step failure (#3433)
Signed-off-by: Ash Wu <[email protected]>
2026-02-25 21:32:18 +08:00
Tomek Czajecki 110055d943 Add time estimates to go command spinners (#3428)
* [eas-cli] Add time estimates to go command spinners

* Fix formatting
2026-02-25 12:27:36 +02:00
Tomek Czajecki 06e2ba5bdb Update eas go fyi link (#3430) 2026-02-25 12:27:11 +02:00
Ash Wu 15690e7f6b [build-tools] Add eas/report_maestro_test_results build function (#3388)
Signed-off-by: Ash Wu <[email protected]>
2026-02-25 17:31:18 +08:00
Expo CI 134e74f50b update CHANGELOG.md 2026-02-25 00:42:47 +00:00
Expo CI 4b3dcda99d v18.0.5 2026-02-25 00:40:34 +00:00
19 changed files with 1524 additions and 107 deletions

No files matched your search

+6 -2
View File
@@ -6,14 +6,18 @@ This is the log of notable changes to EAS CLI and related packages.
### 🛠 Breaking changes
- Require --environment flag for update commands when SDK >= 55. ([#3398](https://github.com/expo/eas-cli/pull/3398) by [@douglowder](https://github.com/douglowder))
### 🎉 New features
### 🐛 Bug fixes
### 🧹 Chores
## [18.0.5](https://github.com/expo/eas-cli/releases/tag/v18.0.5) - 2026-02-25
### 🛠 Breaking changes
- Require --environment flag for update commands when SDK >= 55. ([#3398](https://github.com/expo/eas-cli/pull/3398) by [@douglowder](https://github.com/douglowder))
## [18.0.4](https://github.com/expo/eas-cli/releases/tag/v18.0.4) - 2026-02-23
### 🐛 Bug fixes
+1 -1
View File
@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"version": "18.0.4",
"version": "18.0.6",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@expo/build-tools",
"version": "18.0.2",
"version": "18.0.6",
"bugs": "https://github.com/expo/eas-cli/issues",
"license": "BUSL-1.1",
"author": "Expo <[email protected]>",
@@ -53,6 +53,7 @@
"@urql/core": "^6.0.1",
"bplist-parser": "0.3.2",
"fast-glob": "^3.3.2",
"fast-xml-parser": "^4.4.1",
"fs-extra": "^11.2.0",
"gql.tada": "^1.8.13",
"joi": "^17.13.1",
@@ -10,6 +10,7 @@ import {
} from '@expo/eas-build-job';
import { bunyan } from '@expo/logger';
import { BuildRuntimePlatform, ExternalBuildContextProvider } from '@expo/steps';
import { Client } from '@urql/core';
import assert from 'assert';
import path from 'path';
@@ -53,6 +54,7 @@ export class CustomBuildContext<TJob extends Job = Job> implements ExternalBuild
public readonly startTime: Date;
public readonly logger: bunyan;
public readonly graphqlClient: Client;
public readonly runtimeApi: BuilderRuntimeApi;
public job: TJob;
public metadata?: Metadata;
@@ -65,6 +67,7 @@ export class CustomBuildContext<TJob extends Job = Job> implements ExternalBuild
this.metadata = buildCtx.metadata;
this.logger = buildCtx.logger.child({ phase: BuildPhase.CUSTOM });
this.graphqlClient = buildCtx.graphqlClient;
this.projectSourceDirectory = path.join(buildCtx.workingdir, 'temporary-custom-build');
this.projectTargetDirectory = path.join(buildCtx.workingdir, 'build');
this.defaultWorkingDirectory = buildCtx.getReactNativeProjectDirectory();
@@ -21,6 +21,7 @@ import { createInternalEasMaestroTestFunction } from './functions/internalMaestr
import { createPrebuildBuildFunction } from './functions/prebuild';
import { createReadIpaInfoBuildFunction } from './functions/readIpaInfo';
import { createRepackBuildFunction } from './functions/repack';
import { createReportMaestroTestResultsFunction } from './functions/reportMaestroTestResults';
import { resolveAppleTeamIdFromCredentialsFunction } from './functions/resolveAppleTeamIdFromCredentials';
import { createResolveBuildConfigBuildFunction } from './functions/resolveBuildConfig';
import {
@@ -81,6 +82,8 @@ export function getEasFunctions(ctx: CustomBuildContext): BuildFunction[] {
createUploadToAscBuildFunction(),
createInternalEasMaestroTestFunction(ctx),
createReportMaestroTestResultsFunction(ctx),
];
if (ctx.hasBuildJob()) {
@@ -0,0 +1,98 @@
import { spawnAsync } from '@expo/steps';
import { vol } from 'memfs';
import os from 'os';
import { createGlobalContextMock } from '../../../__tests__/utils/context';
import { createMockLogger } from '../../../__tests__/utils/logger';
import { IosSimulatorUtils } from '../../../utils/IosSimulatorUtils';
import { findMaestroPathsFlowsToExecuteAsync } from '../../../utils/findMaestroPathsFlowsToExecuteAsync';
import { createInternalEasMaestroTestFunction } from '../internalMaestroTest';
jest.mock('@expo/steps', () => ({
...jest.requireActual('@expo/steps'),
spawnAsync: jest.fn(),
}));
jest.mock('../../../utils/IosSimulatorUtils');
jest.mock('../../../utils/AndroidEmulatorUtils');
jest.mock('../../../utils/findMaestroPathsFlowsToExecuteAsync');
const mockedSpawnAsync = jest.mocked(spawnAsync);
const mockedIosUtils = jest.mocked(IosSimulatorUtils);
const mockedFindFlows = jest.mocked(findMaestroPathsFlowsToExecuteAsync);
describe(createInternalEasMaestroTestFunction, () => {
const mockUploadArtifact = jest.fn();
beforeEach(() => {
vol.mkdirSync(os.tmpdir(), { recursive: true });
mockedSpawnAsync.mockResolvedValue(undefined as any);
mockUploadArtifact.mockResolvedValue({ artifactId: null });
mockedIosUtils.getAvailableDevicesAsync.mockResolvedValue([
{ name: 'iPhone 15', udid: 'test-udid-123' } as any,
]);
mockedIosUtils.cloneAsync.mockResolvedValue(undefined as any);
mockedIosUtils.startAsync.mockResolvedValue({ udid: 'cloned-udid' } as any);
mockedIosUtils.waitForReadyAsync.mockResolvedValue(undefined as any);
mockedIosUtils.collectLogsAsync.mockResolvedValue({ outputPath: '/tmp/logs.txt' } as any);
mockedIosUtils.deleteAsync.mockResolvedValue(undefined as any);
mockedFindFlows.mockResolvedValue(['/project/.maestro/home.yml']);
});
function createStep(overrides?: { callInputs?: Record<string, unknown> }) {
const ctx = {
runtimeApi: { uploadArtifact: mockUploadArtifact },
};
const fn = createInternalEasMaestroTestFunction(ctx as any);
return fn.createBuildStepFromFunctionCall(
createGlobalContextMock({
logger: createMockLogger(),
}),
{
callInputs: {
platform: 'ios',
flow_paths: JSON.stringify(['.maestro']),
...overrides?.callInputs,
},
}
);
}
it('sets junit_report_directory output when output_format is junit', async () => {
const step = createStep({
callInputs: { output_format: 'junit' },
});
await step.executeAsync();
const junitDir = step.getOutputValueByName('junit_report_directory');
expect(junitDir).toMatch(/maestro-reports-/);
});
it('does not set junit_report_directory output when output_format is not junit', async () => {
const step = createStep();
await step.executeAsync();
const junitDir = step.getOutputValueByName('junit_report_directory');
expect(junitDir).toBeUndefined();
});
it('does not set junit_report_directory when function throws before reaching output code', async () => {
mockedIosUtils.getAvailableDevicesAsync.mockResolvedValue([]);
const step = createStep({
callInputs: { output_format: 'junit' },
});
try {
await step.executeAsync();
} catch {
// Expected - no booted device found
}
const junitDir = step.getOutputValueByName('junit_report_directory');
expect(junitDir).toBeUndefined();
});
});
@@ -0,0 +1,594 @@
import { vol } from 'memfs';
import {
parseFlowMetadata,
parseJUnitTestCases,
parseMaestroResults,
} from '../maestroResultParser';
describe(parseFlowMetadata, () => {
it('parses valid ai-*.json', async () => {
vol.fromJSON({
'/tests/2026-01-28_055409/ai-home.json': JSON.stringify({
flow_name: 'home',
flow_file_path: '/Users/expo/workingdir/build/.maestro/home.yml',
}),
});
const result = await parseFlowMetadata('/tests/2026-01-28_055409/ai-home.json');
expect(result).toEqual({
flow_name: 'home',
flow_file_path: '/Users/expo/workingdir/build/.maestro/home.yml',
});
});
it('returns null when flow_name is missing', async () => {
vol.fromJSON({
'/tests/2026-01-28_055409/ai-home.json': JSON.stringify({
flow_file_path: '/Users/expo/workingdir/build/.maestro/home.yml',
}),
});
const result = await parseFlowMetadata('/tests/2026-01-28_055409/ai-home.json');
expect(result).toBeNull();
});
it('returns null when flow_file_path is missing', async () => {
vol.fromJSON({
'/tests/2026-01-28_055409/ai-home.json': JSON.stringify({
flow_name: 'home',
}),
});
const result = await parseFlowMetadata('/tests/2026-01-28_055409/ai-home.json');
expect(result).toBeNull();
});
it('returns null for invalid JSON', async () => {
vol.fromJSON({
'/tests/2026-01-28_055409/ai-home.json': 'not json',
});
const result = await parseFlowMetadata('/tests/2026-01-28_055409/ai-home.json');
expect(result).toBeNull();
});
});
describe(parseMaestroResults, () => {
it('parses JUnit results and enriches with ai-*.json metadata', async () => {
vol.fromJSON({
// JUnit XML (primary data)
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="2" failures="1">',
' <testcase id="home" name="home" classname="home" time="10.5" status="SUCCESS"/>',
' <testcase id="login" name="login" classname="login" time="5.0" status="ERROR">',
' <failure>Tap failed</failure>',
' </testcase>',
' </testsuite>',
'</testsuites>',
].join('\n'),
// Debug output (for flow_file_path)
'/tests/2026-01-28_055409/ai-home.json': JSON.stringify({
flow_name: 'home',
flow_file_path: '/root/project/.maestro/home.yml',
}),
'/tests/2026-01-28_055409/ai-login.json': JSON.stringify({
flow_name: 'login',
flow_file_path: '/root/project/.maestro/login.yml',
}),
});
const results = await parseMaestroResults('/junit', '/tests', '/root/project');
expect(results).toHaveLength(2);
expect(results).toEqual(
expect.arrayContaining([
{
name: 'home',
path: '.maestro/home.yml',
status: 'passed',
errorMessage: null,
duration: 10500,
retryCount: 0,
tags: [],
properties: {},
},
{
name: 'login',
path: '.maestro/login.yml',
status: 'failed',
errorMessage: 'Tap failed',
duration: 5000,
retryCount: 0,
tags: [],
properties: {},
},
])
);
});
it('calculates retryCount from timestamp directory occurrences', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" time="10.0" status="SUCCESS"/>',
' </testsuite>',
'</testsuites>',
].join('\n'),
// Two timestamp dirs = 1 retry
'/tests/2026-01-28_055409/ai-home.json': JSON.stringify({
flow_name: 'home',
flow_file_path: '/root/project/.maestro/home.yml',
}),
'/tests/2026-01-28_055420/ai-home.json': JSON.stringify({
flow_name: 'home',
flow_file_path: '/root/project/.maestro/home.yml',
}),
});
const results = await parseMaestroResults('/junit', '/tests', '/root/project');
expect(results[0].retryCount).toBe(1);
});
it('uses flow name as fallback path when ai-*.json not found', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" time="10.0" status="SUCCESS"/>',
' </testsuite>',
'</testsuites>',
].join('\n'),
// No ai-*.json files
});
const results = await parseMaestroResults('/junit', '/tests', '/root/project');
expect(results[0].path).toBe('home');
expect(results[0].retryCount).toBe(0);
});
it('returns empty array when no JUnit files found', async () => {
vol.fromJSON({
'/junit/.gitkeep': '',
'/tests/2026-01-28_055409/ai-home.json': JSON.stringify({
flow_name: 'home',
flow_file_path: '/root/project/.maestro/home.yml',
}),
});
const results = await parseMaestroResults('/junit', '/tests', '/root/project');
expect(results).toEqual([]);
});
it('extracts tags from JUnit properties and separates from other properties', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" time="10.0" status="SUCCESS">',
' <properties>',
' <property name="env" value="staging"/>',
' <property name="tags" value="e2e, smoke"/>',
' </properties>',
' </testcase>',
' </testsuite>',
'</testsuites>',
].join('\n'),
'/tests/2026-01-28_055409/ai-home.json': JSON.stringify({
flow_name: 'home',
flow_file_path: '/root/project/.maestro/home.yml',
}),
});
const results = await parseMaestroResults('/junit', '/tests', '/root/project');
expect(results[0].tags).toEqual(['e2e', 'smoke']);
expect(results[0].properties).toEqual({ env: 'staging' });
});
it('handles reuse_devices=true (junit_report_directory == tests_directory)', async () => {
vol.fromJSON({
// Same directory for both JUnit and debug output
'/maestro-tests/android-maestro-junit.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" time="10.0" status="SUCCESS"/>',
' </testsuite>',
'</testsuites>',
].join('\n'),
'/maestro-tests/2026-01-28_055409/ai-home.json': JSON.stringify({
flow_name: 'home',
flow_file_path: '/root/project/.maestro/home.yml',
}),
});
const results = await parseMaestroResults('/maestro-tests', '/maestro-tests', '/root/project');
expect(results).toEqual([
expect.objectContaining({
name: 'home',
path: '.maestro/home.yml',
status: 'passed',
}),
]);
});
it('handles reuse_devices=false (separate junit_report_directory)', async () => {
vol.fromJSON({
// JUnit in temp dir (per-flow files)
'/tmp/maestro-reports-abc123/junit-report-flow-1.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" time="10.0" status="SUCCESS"/>',
' </testsuite>',
'</testsuites>',
].join('\n'),
'/tmp/maestro-reports-abc123/junit-report-flow-2.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="1">',
' <testcase id="login" name="login" classname="login" time="5.0" status="ERROR">',
' <failure>Failed</failure>',
' </testcase>',
' </testsuite>',
'</testsuites>',
].join('\n'),
// Debug output in default location
'/tests/2026-01-28_055409/ai-home.json': JSON.stringify({
flow_name: 'home',
flow_file_path: '/root/project/.maestro/home.yml',
}),
'/tests/2026-01-28_055409/ai-login.json': JSON.stringify({
flow_name: 'login',
flow_file_path: '/root/project/.maestro/login.yml',
}),
});
const results = await parseMaestroResults(
'/tmp/maestro-reports-abc123',
'/tests',
'/root/project'
);
expect(results).toHaveLength(2);
expect(results).toEqual(
expect.arrayContaining([
expect.objectContaining({ name: 'home', path: '.maestro/home.yml', status: 'passed' }),
expect.objectContaining({ name: 'login', path: '.maestro/login.yml', status: 'failed' }),
])
);
});
it('uses raw path when flow_file_path is outside project_root', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" time="10.0" status="SUCCESS"/>',
' </testsuite>',
'</testsuites>',
].join('\n'),
'/tests/2026-01-28_055409/ai-home.json': JSON.stringify({
flow_name: 'home',
flow_file_path: '/somewhere/else/.maestro/home.yml',
}),
});
const results = await parseMaestroResults('/junit', '/tests', '/root/project');
expect(results[0].path).toBe('/somewhere/else/.maestro/home.yml');
});
it('filters out non-timestamp directories when scanning debug output', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" time="10.0" status="SUCCESS"/>',
' </testsuite>',
'</testsuites>',
].join('\n'),
'/tests/2026-01-28_055409/ai-home.json': JSON.stringify({
flow_name: 'home',
flow_file_path: '/root/project/.maestro/home.yml',
}),
'/tests/not-a-timestamp/ai-home.json': JSON.stringify({
flow_name: 'home',
flow_file_path: '/root/project/.maestro/home.yml',
}),
});
const results = await parseMaestroResults('/junit', '/tests', '/root/project');
// Only 1 occurrence (not-a-timestamp dir should be ignored)
expect(results[0].retryCount).toBe(0);
});
});
describe(parseJUnitTestCases, () => {
it('parses a single testcase with SUCCESS status', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" device="emulator-5554" tests="1" failures="0" time="10.5">',
' <testcase id="home" name="home" classname="home" time="10.5" status="SUCCESS">',
' </testcase>',
' </testsuite>',
'</testsuites>',
].join('\n'),
});
const results = await parseJUnitTestCases('/junit');
expect(results).toEqual([
{
name: 'home',
status: 'passed',
duration: 10500,
errorMessage: null,
tags: [],
properties: {},
},
]);
});
it('parses a failed testcase with failure message', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="1">',
' <testcase id="login" name="login" classname="login" time="5.0" status="ERROR">',
' <failure>Element not visible</failure>',
' </testcase>',
' </testsuite>',
'</testsuites>',
].join('\n'),
});
const results = await parseJUnitTestCases('/junit');
expect(results).toEqual([
{
name: 'login',
status: 'failed',
duration: 5000,
errorMessage: 'Element not visible',
tags: [],
properties: {},
},
]);
});
it('extracts properties from testcase', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" time="10.0" status="SUCCESS">',
' <properties>',
' <property name="env" value="staging"/>',
' <property name="priority" value="high"/>',
' </properties>',
' </testcase>',
' </testsuite>',
'</testsuites>',
].join('\n'),
});
const results = await parseJUnitTestCases('/junit');
expect(results[0].properties).toEqual({ env: 'staging', priority: 'high' });
});
it('extracts tags from "tags" property as comma-separated values', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" time="10.0" status="SUCCESS">',
' <properties>',
' <property name="tags" value="smoke, critical, auth"/>',
' <property name="priority" value="high"/>',
' </properties>',
' </testcase>',
' </testsuite>',
'</testsuites>',
].join('\n'),
});
const results = await parseJUnitTestCases('/junit');
expect(results[0].tags).toEqual(['smoke', 'critical', 'auth']);
expect(results[0].properties).toEqual({ priority: 'high' });
});
it('returns empty tags when no "tags" property exists (older Maestro)', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" time="10.0" status="SUCCESS">',
' <properties>',
' <property name="priority" value="high"/>',
' </properties>',
' </testcase>',
' </testsuite>',
'</testsuites>',
].join('\n'),
});
const results = await parseJUnitTestCases('/junit');
expect(results[0].tags).toEqual([]);
expect(results[0].properties).toEqual({ priority: 'high' });
});
it('parses multiple testcases across multiple testsuites (shards)', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" device="emulator-5554" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" time="10.0" status="SUCCESS"/>',
' </testsuite>',
' <testsuite name="Test Suite" device="emulator-5556" tests="1" failures="1">',
' <testcase id="login" name="login" classname="login" time="5.0" status="ERROR">',
' <failure>Tap failed</failure>',
' </testcase>',
' </testsuite>',
'</testsuites>',
].join('\n'),
});
const results = await parseJUnitTestCases('/junit');
expect(results).toHaveLength(2);
expect(results).toEqual(
expect.arrayContaining([
expect.objectContaining({ name: 'home', status: 'passed' }),
expect.objectContaining({ name: 'login', status: 'failed', errorMessage: 'Tap failed' }),
])
);
});
it('parses multiple JUnit XML files in the same directory', async () => {
vol.fromJSON({
'/junit/junit-report-flow-1.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" time="10.0" status="SUCCESS"/>',
' </testsuite>',
'</testsuites>',
].join('\n'),
'/junit/junit-report-flow-2.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="1">',
' <testcase id="login" name="login" classname="login" time="5.0" status="ERROR">',
' <failure>Tap failed</failure>',
' </testcase>',
' </testsuite>',
'</testsuites>',
].join('\n'),
});
const results = await parseJUnitTestCases('/junit');
expect(results).toHaveLength(2);
});
it('handles missing time attribute (defaults to 0)', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" status="SUCCESS"/>',
' </testsuite>',
'</testsuites>',
].join('\n'),
});
const results = await parseJUnitTestCases('/junit');
expect(results[0].duration).toBe(0);
});
it('handles invalid time attribute (defaults to 0)', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" time="not_a_number" status="SUCCESS"/>',
' </testsuite>',
'</testsuites>',
].join('\n'),
});
const results = await parseJUnitTestCases('/junit');
expect(results[0].duration).toBe(0);
});
it('uses @_status attribute for pass/fail (not <failure> presence)', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="1">',
' <testcase id="home" name="home" classname="home" time="5.0" status="ERROR"/>',
' </testsuite>',
'</testsuites>',
].join('\n'),
});
const results = await parseJUnitTestCases('/junit');
expect(results[0].status).toBe('failed');
expect(results[0].errorMessage).toBeNull();
});
it('extracts error message from <error> element', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="1">',
' <testcase id="home" name="home" classname="home" time="5.0" status="ERROR">',
' <error>Runtime exception occurred</error>',
' </testcase>',
' </testsuite>',
'</testsuites>',
].join('\n'),
});
const results = await parseJUnitTestCases('/junit');
expect(results[0].status).toBe('failed');
expect(results[0].errorMessage).toBe('Runtime exception occurred');
});
it('returns empty array when directory does not exist', async () => {
const results = await parseJUnitTestCases('/nonexistent');
expect(results).toEqual([]);
});
it('returns empty array when no XML files found', async () => {
vol.fromJSON({ '/junit/.gitkeep': '' });
const results = await parseJUnitTestCases('/junit');
expect(results).toEqual([]);
});
it('skips invalid XML files gracefully', async () => {
vol.fromJSON({
'/junit/bad.xml': 'not xml at all',
'/junit/good.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" time="10.0" status="SUCCESS"/>',
' </testsuite>',
'</testsuites>',
].join('\n'),
});
const results = await parseJUnitTestCases('/junit');
expect(results).toEqual([expect.objectContaining({ name: 'home', status: 'passed' })]);
});
it('handles testcase with no properties element', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" time="10.0" status="SUCCESS"/>',
' </testsuite>',
'</testsuites>',
].join('\n'),
});
const results = await parseJUnitTestCases('/junit');
expect(results[0].properties).toEqual({});
});
});
@@ -0,0 +1,273 @@
import { Client } from '@urql/core';
import { vol } from 'memfs';
import { createGlobalContextMock } from '../../../__tests__/utils/context';
import { createMockLogger } from '../../../__tests__/utils/logger';
import { createReportMaestroTestResultsFunction } from '../reportMaestroTestResults';
const JUNIT_PASS = [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" time="10.0" status="SUCCESS"/>',
' </testsuite>',
'</testsuites>',
].join('\n');
const JUNIT_FAIL = [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="1">',
' <testcase id="home" name="home" classname="home" time="5.0" status="ERROR">',
' <failure>Tap failed</failure>',
' </testcase>',
' </testsuite>',
'</testsuites>',
].join('\n');
const FLOW_AI = JSON.stringify({
flow_name: 'home',
flow_file_path: '/root/project/.maestro/home.yml',
});
describe(createReportMaestroTestResultsFunction, () => {
let mockMutationFn: jest.Mock;
let mockGraphqlClient: Client;
beforeEach(() => {
mockMutationFn = jest.fn();
mockGraphqlClient = {
mutation: jest.fn().mockReturnValue({
toPromise: mockMutationFn,
}),
} as unknown as Client;
});
function createStep(overrides?: {
callInputs?: Record<string, unknown>;
staticContextContent?: Record<string, unknown>;
env?: Record<string, string>;
}) {
const ctx = { graphqlClient: mockGraphqlClient };
const fn = createReportMaestroTestResultsFunction(ctx as any);
const globalCtx = createGlobalContextMock({
logger: createMockLogger(),
projectTargetDirectory: '/root/project',
staticContextContent: {
expoApiServerURL: 'https://api.expo.test',
job: { secrets: { robotAccessToken: 'test-token' } },
...overrides?.staticContextContent,
},
});
globalCtx.updateEnv(overrides?.env ?? { __WORKFLOW_JOB_ID: 'job-uuid-123' });
return fn.createBuildStepFromFunctionCall(globalCtx, {
callInputs: {
junit_report_directory: '/junit',
tests_directory: '/tests',
...overrides?.callInputs,
},
});
}
it('parses JUnit results and calls GraphQL mutation', async () => {
vol.fromJSON({
'/junit/report.xml': JUNIT_PASS,
'/tests/2026-01-28_055409/ai-home.json': FLOW_AI,
});
mockMutationFn.mockResolvedValue({
data: {
workflowDeviceTestCaseResult: {
createWorkflowDeviceTestCaseResults: [{ id: 'id-1' }],
},
},
});
await createStep().executeAsync();
expect(mockGraphqlClient.mutation).toHaveBeenCalledTimes(1);
const [, variables] = (mockGraphqlClient.mutation as jest.Mock).mock.calls[0];
expect(variables.input.workflowJobId).toBe('job-uuid-123');
expect(variables.input.testCaseResults).toEqual([
expect.objectContaining({
name: 'home',
path: '.maestro/home.yml',
status: 'PASSED',
errorMessage: null,
duration: 10000,
retryCount: 0,
properties: {},
}),
]);
});
it('sends tags and properties from JUnit XML', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="1" failures="0">',
' <testcase id="home" name="home" classname="home" time="10.0" status="SUCCESS">',
' <properties>',
' <property name="testCaseId" value="TC-001"/>',
' <property name="priority" value="high"/>',
' <property name="tags" value="e2e"/>',
' </properties>',
' </testcase>',
' </testsuite>',
'</testsuites>',
].join('\n'),
'/tests/2026-01-28_055409/ai-home.json': FLOW_AI,
});
mockMutationFn.mockResolvedValue({
data: {
workflowDeviceTestCaseResult: {
createWorkflowDeviceTestCaseResults: [{ id: 'id-1' }],
},
},
});
await createStep().executeAsync();
const [, variables] = (mockGraphqlClient.mutation as jest.Mock).mock.calls[0];
expect(variables.input.testCaseResults[0].tags).toEqual(['e2e']);
expect(variables.input.testCaseResults[0].properties).toEqual({
testCaseId: 'TC-001',
priority: 'high',
});
});
it('reports failed flow with correct status and errorMessage', async () => {
vol.fromJSON({
'/junit/report.xml': JUNIT_FAIL,
'/tests/2026-01-28_055409/ai-home.json': FLOW_AI,
});
mockMutationFn.mockResolvedValue({
data: {
workflowDeviceTestCaseResult: {
createWorkflowDeviceTestCaseResults: [{ id: 'id-1' }],
},
},
});
await createStep().executeAsync();
const [, variables] = (mockGraphqlClient.mutation as jest.Mock).mock.calls[0];
expect(variables.input.testCaseResults[0]).toEqual(
expect.objectContaining({
status: 'FAILED',
errorMessage: 'Tap failed',
duration: 5000,
})
);
});
// robotAccessToken guard removed — Generic.JobZ requires robotAccessToken (z.string(),
// not optional), so it's always present in workflow jobs. graphqlClient is already
// initialized with the token in BuildContext constructor.
it('skips report when junit_report_directory is empty string (upstream step failed)', async () => {
const step = createStep({
callInputs: { junit_report_directory: '' },
});
await step.executeAsync();
expect(mockGraphqlClient.mutation).not.toHaveBeenCalled();
});
it('skips report when __WORKFLOW_JOB_ID env is not set', async () => {
vol.fromJSON({
'/junit/report.xml': JUNIT_PASS,
'/tests/2026-01-28_055409/ai-home.json': FLOW_AI,
});
const step = createStep({ env: {} });
await step.executeAsync();
expect(mockGraphqlClient.mutation).not.toHaveBeenCalled();
});
it('skips report when no JUnit files found', async () => {
const step = createStep();
await step.executeAsync();
expect(mockGraphqlClient.mutation).not.toHaveBeenCalled();
});
it('does not throw when GraphQL returns error', async () => {
vol.fromJSON({
'/junit/report.xml': JUNIT_PASS,
'/tests/2026-01-28_055409/ai-home.json': FLOW_AI,
});
mockMutationFn.mockResolvedValue({
error: { message: 'Something went wrong' },
});
await createStep().executeAsync();
});
it('does not throw when mutation rejects', async () => {
vol.fromJSON({
'/junit/report.xml': JUNIT_PASS,
'/tests/2026-01-28_055409/ai-home.json': FLOW_AI,
});
mockMutationFn.mockRejectedValue(new Error('Network error'));
await createStep().executeAsync();
});
it('skips report when duplicate testcase names found', async () => {
vol.fromJSON({
'/junit/report.xml': [
'<?xml version="1.0"?>',
'<testsuites>',
' <testsuite name="Test Suite" tests="2" failures="0">',
' <testcase id="login" name="login" classname="login" time="10.0" status="SUCCESS"/>',
' <testcase id="login" name="login" classname="login" time="5.0" status="SUCCESS"/>',
' </testsuite>',
'</testsuites>',
].join('\n'),
'/tests/2026-01-28_055409/ai-login.json': JSON.stringify({
flow_name: 'login',
flow_file_path: '/root/project/.maestro/login.yml',
}),
});
await createStep().executeAsync();
expect(mockGraphqlClient.mutation).not.toHaveBeenCalled();
});
it('uses default directories when inputs are not provided', async () => {
vol.fromJSON({
'/home/expo/.maestro/tests/report.xml': JUNIT_PASS,
'/home/expo/.maestro/tests/2026-01-28_055409/ai-home.json': FLOW_AI,
});
const ctx = { graphqlClient: mockGraphqlClient };
const fn = createReportMaestroTestResultsFunction(ctx as any);
const globalCtx = createGlobalContextMock({
logger: createMockLogger(),
projectTargetDirectory: '/root/project',
staticContextContent: {
expoApiServerURL: 'https://api.expo.test',
job: { secrets: { robotAccessToken: 'test-token' } },
},
});
globalCtx.updateEnv({ __WORKFLOW_JOB_ID: 'job-uuid-123', HOME: '/home/expo' });
mockMutationFn.mockResolvedValue({
data: {
workflowDeviceTestCaseResult: {
createWorkflowDeviceTestCaseResults: [{ id: 'id-1' }],
},
},
});
const step = fn.createBuildStepFromFunctionCall(globalCtx, {
callInputs: {},
});
await step.executeAsync();
expect(mockGraphqlClient.mutation).toHaveBeenCalledTimes(1);
});
});
@@ -81,6 +81,10 @@ export function createInternalEasMaestroTestFunction(ctx: CustomBuildContext): B
id: 'test_reports_artifact_id',
required: false,
}),
BuildStepOutput.createProvider({
id: 'junit_report_directory',
required: false,
}),
],
fn: async (stepCtx, { inputs: _inputs, env, outputs }) => {
// inputs come in form of { value: unknown }. Here we parse them into a typed and validated object.
@@ -348,6 +352,10 @@ export function createInternalEasMaestroTestFunction(ctx: CustomBuildContext): B
}
}
if (output_format === 'junit') {
outputs.junit_report_directory.set(maestroReportsDir);
}
const generatedDeviceLogs = await fs.promises.readdir(deviceLogsDir);
if (generatedDeviceLogs.length === 0) {
stepCtx.logger.warn('No device logs were successfully collected.');
@@ -0,0 +1,264 @@
import { XMLParser } from 'fast-xml-parser';
import fs from 'fs/promises';
import path from 'path';
import { z } from 'zod';
export interface MaestroFlowResult {
name: string;
path: string;
status: 'passed' | 'failed';
errorMessage: string | null;
duration: number; // milliseconds
retryCount: number;
tags: string[];
properties: Record<string, string>;
}
// Maestro's TestDebugReporter creates timestamped directories, e.g. "2024-06-15_143022"
const TIMESTAMP_DIR_PATTERN = /^\d{4}-\d{2}-\d{2}_\d{6}$/;
export function extractFlowKey(filename: string, prefix: string): string | null {
const match = filename.match(new RegExp(`^${prefix}-(.+)\\.json$`));
return match?.[1] ?? null;
}
export interface JUnitTestCaseResult {
name: string;
status: 'passed' | 'failed';
duration: number; // milliseconds
errorMessage: string | null;
tags: string[];
properties: Record<string, string>;
}
const xmlParser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '@_',
// Ensure single-element arrays are always arrays
isArray: name => ['testsuite', 'testcase', 'property'].includes(name),
});
export async function parseJUnitTestCases(junitDirectory: string): Promise<JUnitTestCaseResult[]> {
let entries: string[];
try {
entries = await fs.readdir(junitDirectory);
} catch {
return [];
}
const xmlFiles = entries.filter(f => f.endsWith('.xml'));
if (xmlFiles.length === 0) {
return [];
}
const results: JUnitTestCaseResult[] = [];
for (const xmlFile of xmlFiles) {
try {
const content = await fs.readFile(path.join(junitDirectory, xmlFile), 'utf-8');
const parsed = xmlParser.parse(content);
const testsuites = parsed?.testsuites?.testsuite;
if (!Array.isArray(testsuites)) {
continue;
}
for (const suite of testsuites) {
const testcases = suite?.testcase;
if (!Array.isArray(testcases)) {
continue;
}
for (const tc of testcases) {
const name = tc['@_name'];
if (!name) {
continue;
}
const timeStr = tc['@_time'];
const timeSeconds = timeStr ? parseFloat(timeStr) : 0;
const duration = Number.isFinite(timeSeconds) ? Math.round(timeSeconds * 1000) : 0;
// Use @_status as primary indicator (more robust than checking <failure> presence)
const status: 'passed' | 'failed' = tc['@_status'] === 'SUCCESS' ? 'passed' : 'failed';
// Extract error message from <failure> or <error> elements
const failureText =
tc.failure != null
? typeof tc.failure === 'string'
? tc.failure
: (tc.failure?.['#text'] ?? null)
: null;
const errorText =
tc.error != null
? typeof tc.error === 'string'
? tc.error
: (tc.error?.['#text'] ?? null)
: null;
const errorMessage: string | null = failureText ?? errorText ?? null;
// Extract properties
const rawProperties: { '@_name': string; '@_value': string }[] =
tc.properties?.property ?? [];
const properties: Record<string, string> = {};
for (const prop of rawProperties) {
const propName = prop['@_name'];
const value = prop['@_value'];
if (typeof propName !== 'string' || typeof value !== 'string') {
continue;
}
properties[propName] = value;
}
// Extract tags from "tags" property (Maestro 2.2.0+, comma-separated)
const tagsValue = properties['tags'];
const tags: string[] = tagsValue
? tagsValue
.split(',')
.map(t => t.trim())
.filter(Boolean)
: [];
delete properties['tags'];
results.push({ name, status, duration, errorMessage, tags, properties });
}
}
} catch {
// Skip malformed XML files
continue;
}
}
return results;
}
const FlowMetadataFileSchema = z.object({
flow_name: z.string(),
flow_file_path: z.string(),
});
type FlowMetadata = z.output<typeof FlowMetadataFileSchema>;
/**
* Parses an `ai-*.json` file produced by Maestro's TestDebugReporter.
*
* The file contains:
* - `flow_name`: derived from the YAML `config.name` field if present, otherwise
* the flow filename without extension.
* See: https://github.com/mobile-dev-inc/Maestro/blob/c0e95fd/maestro-cli/src/main/java/maestro/cli/runner/TestRunner.kt#L70
* - `flow_file_path`: absolute path to the original flow YAML file.
* - `outputs`: screenshot defect data (unused here).
*
* Filename format: `ai-(flowName).json` where `/` in flowName is replaced with `_`.
* See: https://github.com/mobile-dev-inc/Maestro/blob/c0e95fd/maestro-cli/src/main/java/maestro/cli/report/TestDebugReporter.kt#L67
*/
export async function parseFlowMetadata(filePath: string): Promise<FlowMetadata | null> {
try {
const content = await fs.readFile(filePath, 'utf-8');
const data = JSON.parse(content);
return FlowMetadataFileSchema.parse(data);
} catch {
return null;
}
}
export async function parseMaestroResults(
junitDirectory: string,
testsDirectory: string,
projectRoot: string
): Promise<MaestroFlowResult[]> {
// 1. Parse JUnit XML files (primary source)
const junitResults = await parseJUnitTestCases(junitDirectory);
if (junitResults.length === 0) {
return [];
}
// 2. Parse ai-*.json from debug output for flow_file_path + retryCount
const flowPathMap = new Map<string, string>(); // flowName → flowFilePath
const flowOccurrences = new Map<string, number>(); // flowName → count
let entries: string[];
try {
entries = await fs.readdir(testsDirectory);
} catch {
entries = [];
}
const timestampDirs = entries.filter(name => TIMESTAMP_DIR_PATTERN.test(name)).sort();
for (const dir of timestampDirs) {
const dirPath = path.join(testsDirectory, dir);
let files: string[];
try {
files = await fs.readdir(dirPath);
} catch {
continue;
}
for (const file of files) {
const flowKey = extractFlowKey(file, 'ai');
if (!flowKey) {
continue;
}
const metadata = await parseFlowMetadata(path.join(dirPath, file));
if (!metadata) {
continue;
}
// Track latest path (last timestamp dir wins)
flowPathMap.set(metadata.flow_name, metadata.flow_file_path);
// Count occurrences for retryCount
flowOccurrences.set(metadata.flow_name, (flowOccurrences.get(metadata.flow_name) ?? 0) + 1);
}
}
// 3. Merge: JUnit results + ai-*.json metadata
const results: MaestroFlowResult[] = [];
for (const junit of junitResults) {
const flowFilePath = flowPathMap.get(junit.name);
const relativePath = flowFilePath
? await relativizePathAsync(flowFilePath, projectRoot)
: junit.name; // fallback: use flow name if ai-*.json not found
const occurrences = flowOccurrences.get(junit.name) ?? 0;
const retryCount = Math.max(0, occurrences - 1);
results.push({
name: junit.name,
path: relativePath,
status: junit.status,
errorMessage: junit.errorMessage,
duration: junit.duration,
retryCount,
tags: junit.tags,
properties: junit.properties,
});
}
return results;
}
async function relativizePathAsync(flowFilePath: string, projectRoot: string): Promise<string> {
if (!path.isAbsolute(flowFilePath)) {
return flowFilePath;
}
// Resolve symlinks (e.g., /tmp -> /private/tmp on macOS) for consistent comparison
let resolvedRoot = projectRoot;
let resolvedFlow = flowFilePath;
try {
resolvedRoot = await fs.realpath(projectRoot);
} catch {}
try {
resolvedFlow = await fs.realpath(flowFilePath);
} catch {}
const relative = path.relative(resolvedRoot, resolvedFlow);
if (relative.startsWith('..')) {
return flowFilePath;
}
return relative;
}
@@ -0,0 +1,120 @@
import { BuildFunction, BuildStepInput, BuildStepInputValueTypeName } from '@expo/steps';
import { graphql } from 'gql.tada';
import { MaestroFlowResult, parseMaestroResults } from './maestroResultParser';
import { CustomBuildContext } from '../../customBuildContext';
const CREATE_MUTATION = graphql(`
mutation CreateWorkflowDeviceTestCaseResults($input: CreateWorkflowDeviceTestCaseResultsInput!) {
workflowDeviceTestCaseResult {
createWorkflowDeviceTestCaseResults(input: $input) {
id
}
}
}
`);
const FLOW_STATUS_TO_TEST_CASE_RESULT_STATUS: Record<string, 'PASSED' | 'FAILED' | undefined> = {
passed: 'PASSED',
failed: 'FAILED',
} satisfies Record<MaestroFlowResult['status'], 'PASSED' | 'FAILED'>;
export function createReportMaestroTestResultsFunction(ctx: CustomBuildContext): BuildFunction {
return new BuildFunction({
namespace: 'eas',
id: 'report_maestro_test_results',
name: 'Report Maestro Test Results',
__metricsId: 'eas/report_maestro_test_results',
inputProviders: [
BuildStepInput.createProvider({
id: 'junit_report_directory',
required: false,
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
defaultValue: '${{ env.HOME }}/.maestro/tests',
}),
BuildStepInput.createProvider({
id: 'tests_directory',
required: false,
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
defaultValue: '${{ env.HOME }}/.maestro/tests',
}),
],
fn: async (stepsCtx, { inputs }) => {
const { logger } = stepsCtx;
const workflowJobId = stepsCtx.global.env.__WORKFLOW_JOB_ID;
if (!workflowJobId) {
logger.info('Not running in a workflow job, skipping test results report');
return;
}
const junitDirectory = (inputs.junit_report_directory.value as string | undefined) ?? '';
if (!junitDirectory) {
logger.info('No JUnit directory provided, skipping test results report');
return;
}
const testsDirectory = inputs.tests_directory.value as string;
try {
const flowResults = await parseMaestroResults(
junitDirectory,
testsDirectory,
stepsCtx.workingDirectory
);
if (flowResults.length === 0) {
logger.info('No maestro test results found, skipping report');
return;
}
// Maestro allows overriding flow names via config, so different flow files can share
// the same name. JUnit XML only contains names (not file paths), making it impossible
// to map duplicates back to their original flow files. Skip and let the user fix it.
const names = flowResults.map(r => r.name);
const duplicates = names.filter((n, i) => names.indexOf(n) !== i);
if (duplicates.length > 0) {
logger.error(
`Duplicate test case names found in JUnit output: ${[...new Set(duplicates)].join(
', '
)}. Skipping report. Ensure each Maestro flow has a unique name.`
);
return;
}
const testCaseResults = flowResults.flatMap(f => {
const status = FLOW_STATUS_TO_TEST_CASE_RESULT_STATUS[f.status];
if (!status) {
return [];
}
return [
{
name: f.name,
path: f.path,
status,
errorMessage: f.errorMessage,
duration: f.duration,
retryCount: f.retryCount,
tags: f.tags,
properties: f.properties,
},
];
});
const result = await ctx.graphqlClient
.mutation(CREATE_MUTATION, {
input: {
workflowJobId,
testCaseResults,
},
})
.toPromise();
if (result.error) {
logger.error({ error: result.error }, 'GraphQL error creating test case results');
return;
}
logger.info(`Reported ${testCaseResults.length} test case result(s).`);
} catch (error) {
logger.error({ err: error }, 'Failed to create test case results');
}
},
});
}
+92 -90
View File
@@ -178,7 +178,7 @@ ALIASES
$ eas login
```
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/account/login.ts)_
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/account/login.ts)_
## `eas account:logout`
@@ -195,7 +195,7 @@ ALIASES
$ eas logout
```
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/account/logout.ts)_
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/account/logout.ts)_
## `eas account:usage [ACCOUNT_NAME]`
@@ -217,7 +217,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/v18.0.4/packages/eas-cli/src/commands/account/usage.ts)_
_See code: [packages/eas-cli/src/commands/account/usage.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/account/usage.ts)_
## `eas account:view`
@@ -234,7 +234,7 @@ ALIASES
$ eas whoami
```
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/account/view.ts)_
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/account/view.ts)_
## `eas analytics [STATUS]`
@@ -248,7 +248,7 @@ DESCRIPTION
display or change analytics settings
```
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/analytics.ts)_
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/analytics.ts)_
## `eas autocomplete [SHELL]`
@@ -300,7 +300,7 @@ DESCRIPTION
create a branch
```
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/branch/create.ts)_
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/branch/create.ts)_
## `eas branch:delete [NAME]`
@@ -321,7 +321,7 @@ DESCRIPTION
delete a branch
```
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/branch/delete.ts)_
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/branch/delete.ts)_
## `eas branch:list`
@@ -341,7 +341,7 @@ DESCRIPTION
list all branches
```
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/branch/list.ts)_
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/branch/list.ts)_
## `eas branch:rename`
@@ -361,7 +361,7 @@ DESCRIPTION
rename a branch
```
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/branch/rename.ts)_
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/branch/rename.ts)_
## `eas branch:view [NAME]`
@@ -384,7 +384,7 @@ DESCRIPTION
view a branch
```
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/branch/view.ts)_
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/branch/view.ts)_
## `eas build`
@@ -425,7 +425,7 @@ DESCRIPTION
start a build
```
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/index.ts)_
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/build/index.ts)_
## `eas build:cancel [BUILD_ID]`
@@ -444,7 +444,7 @@ DESCRIPTION
cancel a build
```
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/cancel.ts)_
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/build/cancel.ts)_
## `eas build:configure`
@@ -461,7 +461,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/v18.0.4/packages/eas-cli/src/commands/build/configure.ts)_
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/build/configure.ts)_
## `eas build:delete [BUILD_ID]`
@@ -480,7 +480,7 @@ DESCRIPTION
delete a build
```
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/delete.ts)_
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/build/delete.ts)_
## `eas build:dev`
@@ -500,7 +500,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/v18.0.4/packages/eas-cli/src/commands/build/dev.ts)_
_See code: [packages/eas-cli/src/commands/build/dev.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/build/dev.ts)_
## `eas build:download`
@@ -521,7 +521,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/v18.0.4/packages/eas-cli/src/commands/build/download.ts)_
_See code: [packages/eas-cli/src/commands/build/download.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/build/download.ts)_
## `eas build:inspect`
@@ -556,7 +556,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/v18.0.4/packages/eas-cli/src/commands/build/inspect.ts)_
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/build/inspect.ts)_
## `eas build:list`
@@ -608,7 +608,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/v18.0.4/packages/eas-cli/src/commands/build/list.ts)_
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/build/list.ts)_
## `eas build:resign`
@@ -637,7 +637,7 @@ DESCRIPTION
re-sign a build archive
```
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/build/resign.ts)_
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/build/resign.ts)_
## `eas build:run`
@@ -663,7 +663,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/v18.0.4/packages/eas-cli/src/commands/build/run.ts)_
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/build/run.ts)_
## `eas build:submit`
@@ -717,7 +717,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/v18.0.4/packages/eas-cli/src/commands/build/version/get.ts)_
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/build/version/get.ts)_
## `eas build:version:set`
@@ -736,7 +736,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/v18.0.4/packages/eas-cli/src/commands/build/version/set.ts)_
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/build/version/set.ts)_
## `eas build:version:sync`
@@ -755,7 +755,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/v18.0.4/packages/eas-cli/src/commands/build/version/sync.ts)_
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/build/version/sync.ts)_
## `eas build:view [BUILD_ID]`
@@ -772,7 +772,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/v18.0.4/packages/eas-cli/src/commands/build/view.ts)_
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/build/view.ts)_
## `eas channel:create [NAME]`
@@ -793,7 +793,7 @@ DESCRIPTION
create a channel
```
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/channel/create.ts)_
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/channel/create.ts)_
## `eas channel:delete [NAME]`
@@ -814,7 +814,7 @@ DESCRIPTION
Delete a channel
```
_See code: [packages/eas-cli/src/commands/channel/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/channel/delete.ts)_
_See code: [packages/eas-cli/src/commands/channel/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/channel/delete.ts)_
## `eas channel:edit [NAME]`
@@ -836,7 +836,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/v18.0.4/packages/eas-cli/src/commands/channel/edit.ts)_
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/channel/edit.ts)_
## `eas channel:list`
@@ -856,7 +856,7 @@ DESCRIPTION
list all channels
```
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/channel/list.ts)_
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/channel/list.ts)_
## `eas channel:pause [NAME]`
@@ -878,7 +878,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/v18.0.4/packages/eas-cli/src/commands/channel/pause.ts)_
_See code: [packages/eas-cli/src/commands/channel/pause.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/channel/pause.ts)_
## `eas channel:resume [NAME]`
@@ -900,7 +900,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/v18.0.4/packages/eas-cli/src/commands/channel/resume.ts)_
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/channel/resume.ts)_
## `eas channel:rollout [CHANNEL]`
@@ -933,7 +933,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/v18.0.4/packages/eas-cli/src/commands/channel/rollout.ts)_
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/channel/rollout.ts)_
## `eas channel:view [NAME]`
@@ -956,7 +956,7 @@ DESCRIPTION
view a channel
```
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/channel/view.ts)_
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/channel/view.ts)_
## `eas config`
@@ -977,7 +977,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/v18.0.4/packages/eas-cli/src/commands/config.ts)_
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/config.ts)_
## `eas credentials`
@@ -994,7 +994,7 @@ DESCRIPTION
manage credentials
```
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/credentials/index.ts)_
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/credentials/index.ts)_
## `eas credentials:configure-build`
@@ -1012,7 +1012,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/v18.0.4/packages/eas-cli/src/commands/credentials/configure-build.ts)_
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/credentials/configure-build.ts)_
## `eas deploy [options]`
@@ -1040,7 +1040,7 @@ ALIASES
$ eas worker:deploy
```
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/deploy/index.ts)_
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/deploy/index.ts)_
## `eas deploy:alias`
@@ -1065,7 +1065,7 @@ ALIASES
$ eas deploy:promote
```
_See code: [packages/eas-cli/src/commands/deploy/alias/index.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/deploy/alias/index.ts)_
_See code: [packages/eas-cli/src/commands/deploy/alias/index.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/deploy/alias/index.ts)_
## `eas deploy:alias:delete [ALIAS_NAME]`
@@ -1086,7 +1086,7 @@ ALIASES
$ eas worker:alias:delete
```
_See code: [packages/eas-cli/src/commands/deploy/alias/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/deploy/alias/delete.ts)_
_See code: [packages/eas-cli/src/commands/deploy/alias/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/deploy/alias/delete.ts)_
## `eas deploy:delete [DEPLOYMENT_ID]`
@@ -1107,7 +1107,7 @@ ALIASES
$ eas worker:delete
```
_See code: [packages/eas-cli/src/commands/deploy/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/deploy/delete.ts)_
_See code: [packages/eas-cli/src/commands/deploy/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/deploy/delete.ts)_
## `eas deploy:promote`
@@ -1144,7 +1144,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/v18.0.4/packages/eas-cli/src/commands/device/create.ts)_
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/device/create.ts)_
## `eas device:delete`
@@ -1164,7 +1164,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/v18.0.4/packages/eas-cli/src/commands/device/delete.ts)_
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/device/delete.ts)_
## `eas device:list`
@@ -1185,7 +1185,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/v18.0.4/packages/eas-cli/src/commands/device/list.ts)_
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/device/list.ts)_
## `eas device:rename`
@@ -1206,7 +1206,7 @@ DESCRIPTION
rename a registered device
```
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/device/rename.ts)_
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/device/rename.ts)_
## `eas device:view [UDID]`
@@ -1220,7 +1220,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/v18.0.4/packages/eas-cli/src/commands/device/view.ts)_
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/device/view.ts)_
## `eas diagnostics`
@@ -1234,7 +1234,7 @@ DESCRIPTION
display environment info
```
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/diagnostics.ts)_
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/diagnostics.ts)_
## `eas env:create [ENVIRONMENT]`
@@ -1264,7 +1264,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/v18.0.4/packages/eas-cli/src/commands/env/create.ts)_
_See code: [packages/eas-cli/src/commands/env/create.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/env/create.ts)_
## `eas env:delete [ENVIRONMENT]`
@@ -1289,7 +1289,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/v18.0.4/packages/eas-cli/src/commands/env/delete.ts)_
_See code: [packages/eas-cli/src/commands/env/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/env/delete.ts)_
## `eas env:exec ENVIRONMENT BASH_COMMAND`
@@ -1311,7 +1311,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/v18.0.4/packages/eas-cli/src/commands/env/exec.ts)_
_See code: [packages/eas-cli/src/commands/env/exec.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/env/exec.ts)_
## `eas env:get [ENVIRONMENT]`
@@ -1336,7 +1336,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/v18.0.4/packages/eas-cli/src/commands/env/get.ts)_
_See code: [packages/eas-cli/src/commands/env/get.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/env/get.ts)_
## `eas env:list [ENVIRONMENT]`
@@ -1362,7 +1362,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/v18.0.4/packages/eas-cli/src/commands/env/list.ts)_
_See code: [packages/eas-cli/src/commands/env/list.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/env/list.ts)_
## `eas env:pull [ENVIRONMENT]`
@@ -1384,7 +1384,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/v18.0.4/packages/eas-cli/src/commands/env/pull.ts)_
_See code: [packages/eas-cli/src/commands/env/pull.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/env/pull.ts)_
## `eas env:push [ENVIRONMENT]`
@@ -1406,7 +1406,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/v18.0.4/packages/eas-cli/src/commands/env/push.ts)_
_See code: [packages/eas-cli/src/commands/env/push.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/env/push.ts)_
## `eas env:update [ENVIRONMENT]`
@@ -1438,7 +1438,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/v18.0.4/packages/eas-cli/src/commands/env/update.ts)_
_See code: [packages/eas-cli/src/commands/env/update.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/env/update.ts)_
## `eas fingerprint:compare [HASH1] [HASH2]`
@@ -1482,7 +1482,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/v18.0.4/packages/eas-cli/src/commands/fingerprint/compare.ts)_
_See code: [packages/eas-cli/src/commands/fingerprint/compare.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/fingerprint/compare.ts)_
## `eas fingerprint:generate`
@@ -1512,7 +1512,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/v18.0.4/packages/eas-cli/src/commands/fingerprint/generate.ts)_
_See code: [packages/eas-cli/src/commands/fingerprint/generate.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/fingerprint/generate.ts)_
## `eas help [COMMAND]`
@@ -1621,7 +1621,7 @@ DESCRIPTION
validate the local store configuration
```
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/metadata/lint.ts)_
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/metadata/lint.ts)_
## `eas metadata:pull`
@@ -1638,7 +1638,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/v18.0.4/packages/eas-cli/src/commands/metadata/pull.ts)_
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/metadata/pull.ts)_
## `eas metadata:push`
@@ -1655,7 +1655,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/v18.0.4/packages/eas-cli/src/commands/metadata/push.ts)_
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/metadata/push.ts)_
## `eas new [PATH]`
@@ -1706,7 +1706,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/v18.0.4/packages/eas-cli/src/commands/open.ts)_
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/open.ts)_
## `eas project:info`
@@ -1720,7 +1720,7 @@ DESCRIPTION
information about the current project
```
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/project/info.ts)_
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/project/info.ts)_
## `eas project:init`
@@ -1743,7 +1743,7 @@ ALIASES
$ eas init
```
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/project/init.ts)_
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/project/init.ts)_
## `eas project:new [PATH]`
@@ -1766,7 +1766,7 @@ ALIASES
$ eas new
```
_See code: [packages/eas-cli/src/commands/project/new.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/project/new.ts)_
_See code: [packages/eas-cli/src/commands/project/new.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/project/new.ts)_
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
@@ -1784,7 +1784,7 @@ ALIASES
$ eas onboarding
```
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/project/onboarding.ts)_
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/project/onboarding.ts)_
## `eas submit`
@@ -1819,7 +1819,7 @@ ALIASES
$ eas build:submit
```
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/submit.ts)_
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/submit.ts)_
## `eas update`
@@ -1841,7 +1841,8 @@ FLAGS
--emit-metadata Emit "eas-update-metadata.json" in the bundle folder with detailed information about
the generated updates
--environment=<value> Environment to use for the server-side defined EAS environment variables during
command execution, e.g. "production", "preview", "development"
command execution, e.g. "production", "preview", "development". Required for
projects using Expo SDK 55 or greater.
--input-dir=<value> [default: dist] Location of the bundle
--json Enable JSON output, non-JSON messages will be printed to stderr.
--non-interactive Run the command in non-interactive mode.
@@ -1859,7 +1860,7 @@ DESCRIPTION
publish an update group
```
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/update/index.ts)_
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/update/index.ts)_
## `eas update:configure`
@@ -1872,14 +1873,15 @@ USAGE
FLAGS
-p, --platform=(android|ios|all) [default: all] Platform to configure
--environment=<value> Environment to use for the server-side defined EAS environment variables during
command execution, e.g. "production", "preview", "development"
command execution, e.g. "production", "preview", "development". Required for
projects using Expo SDK 55 or greater.
--non-interactive Run the command in non-interactive mode.
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/v18.0.4/packages/eas-cli/src/commands/update/configure.ts)_
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/update/configure.ts)_
## `eas update:delete GROUPID`
@@ -1900,7 +1902,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/v18.0.4/packages/eas-cli/src/commands/update/delete.ts)_
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/update/delete.ts)_
## `eas update:edit [GROUPID]`
@@ -1924,7 +1926,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/v18.0.4/packages/eas-cli/src/commands/update/edit.ts)_
_See code: [packages/eas-cli/src/commands/update/edit.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/update/edit.ts)_
## `eas update:list`
@@ -1949,7 +1951,7 @@ DESCRIPTION
view the recent updates
```
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/update/list.ts)_
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/update/list.ts)_
## `eas update:republish`
@@ -1985,7 +1987,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/v18.0.4/packages/eas-cli/src/commands/update/republish.ts)_
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/update/republish.ts)_
## `eas update:revert-update-rollout`
@@ -2012,7 +2014,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/v18.0.4/packages/eas-cli/src/commands/update/revert-update-rollout.ts)_
_See code: [packages/eas-cli/src/commands/update/revert-update-rollout.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/update/revert-update-rollout.ts)_
## `eas update:roll-back-to-embedded`
@@ -2040,7 +2042,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/v18.0.4/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
## `eas update:rollback`
@@ -2061,7 +2063,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/v18.0.4/packages/eas-cli/src/commands/update/rollback.ts)_
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/update/rollback.ts)_
## `eas update:view GROUPID`
@@ -2081,7 +2083,7 @@ DESCRIPTION
update group details
```
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/update/view.ts)_
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/update/view.ts)_
## `eas upload`
@@ -2102,7 +2104,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/v18.0.4/packages/eas-cli/src/commands/upload.ts)_
_See code: [packages/eas-cli/src/commands/upload.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/upload.ts)_
## `eas webhook:create`
@@ -2123,7 +2125,7 @@ DESCRIPTION
create a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/webhook/create.ts)_
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/webhook/create.ts)_
## `eas webhook:delete [ID]`
@@ -2143,7 +2145,7 @@ DESCRIPTION
delete a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/webhook/delete.ts)_
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/webhook/delete.ts)_
## `eas webhook:list`
@@ -2161,7 +2163,7 @@ DESCRIPTION
list webhooks
```
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/webhook/list.ts)_
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/webhook/list.ts)_
## `eas webhook:update`
@@ -2183,7 +2185,7 @@ DESCRIPTION
update a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/webhook/update.ts)_
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/webhook/update.ts)_
## `eas webhook:view ID`
@@ -2200,7 +2202,7 @@ DESCRIPTION
view a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/webhook/view.ts)_
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/webhook/view.ts)_
## `eas whoami`
@@ -2320,7 +2322,7 @@ DESCRIPTION
to cancel.
```
_See code: [packages/eas-cli/src/commands/workflow/cancel.ts](https://github.com/expo/eas-cli/blob/v18.0.4/packages/eas-cli/src/commands/workflow/cancel.ts)_
_See code: [packages/eas-cli/src/commands/workflow/cancel.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/workflow/cancel.ts)_
## `eas workflow:create [NAME]`
@@ -2340,7 +2342,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/v18.0.4/packages/eas-cli/src/commands/workflow/create.ts)_
_See code: [packages/eas-cli/src/commands/workflow/create.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/workflow/create.ts)_
## `eas workflow:logs [ID]`
@@ -2364,7 +2366,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/v18.0.4/packages/eas-cli/src/commands/workflow/logs.ts)_
_See code: [packages/eas-cli/src/commands/workflow/logs.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/workflow/logs.ts)_
## `eas workflow:run [FILE]`
@@ -2404,7 +2406,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/v18.0.4/packages/eas-cli/src/commands/workflow/run.ts)_
_See code: [packages/eas-cli/src/commands/workflow/run.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/workflow/run.ts)_
## `eas workflow:runs`
@@ -2429,7 +2431,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/v18.0.4/packages/eas-cli/src/commands/workflow/runs.ts)_
_See code: [packages/eas-cli/src/commands/workflow/runs.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/workflow/runs.ts)_
## `eas workflow:status [WORKFLOW_RUN_ID]`
@@ -2457,7 +2459,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/v18.0.4/packages/eas-cli/src/commands/workflow/status.ts)_
_See code: [packages/eas-cli/src/commands/workflow/status.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/workflow/status.ts)_
## `eas workflow:validate PATH`
@@ -2477,7 +2479,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/v18.0.4/packages/eas-cli/src/commands/workflow/validate.ts)_
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/workflow/validate.ts)_
## `eas workflow:view [ID]`
@@ -2499,5 +2501,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/v18.0.4/packages/eas-cli/src/commands/workflow/view.ts)_
_See code: [packages/eas-cli/src/commands/workflow/view.ts](https://github.com/expo/eas-cli/blob/v18.0.6/packages/eas-cli/src/commands/workflow/view.ts)_
<!-- commandsstop -->
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "eas-cli",
"version": "18.0.4",
"version": "18.0.6",
"description": "EAS command line tool",
"keywords": [
"cli",
+49 -3
View File
@@ -173,7 +173,7 @@ export default class Go extends EasCommand {
async runAsync(): Promise<void> {
Log.log(
chalk.bold(
`Creating your personal Expo Go and deploying to TestFlight. ${learnMore('https://expo.fyi/personal-expo-go')}`
`Creating your personal Expo Go and deploying to TestFlight. ${learnMore('https://expo.fyi/deploy-expo-go-testflight')}`
)
);
@@ -551,12 +551,34 @@ export default class Go extends EasCommand {
graphqlClient: ExpoGraphqlClient,
workflowRunId: string
): Promise<WorkflowRunStatus> {
const buildSpinner = ora('Building Expo Go').start();
const EXPECTED_BUILD_DURATION_SECONDS = 5 * 60;
const EXPECTED_SUBMIT_DURATION_SECONDS = 2 * 60;
const buildStartTime = Date.now();
let submitStartTime: number | null = null;
const buildSpinner = ora(
this.formatSpinnerText('Building Expo Go', EXPECTED_BUILD_DURATION_SECONDS, buildStartTime)
).start();
let submitSpinner: ReturnType<typeof ora> | null = null;
let buildCompleted = false;
let failedFetchesCount = 0;
while (true) {
if (!buildCompleted) {
buildSpinner.text = this.formatSpinnerText(
'Building Expo Go',
EXPECTED_BUILD_DURATION_SECONDS,
buildStartTime
);
}
if (submitSpinner && submitStartTime) {
submitSpinner.text = this.formatSpinnerText(
'Submitting to TestFlight',
EXPECTED_SUBMIT_DURATION_SECONDS,
submitStartTime
);
}
try {
const workflowRun = await WorkflowRunQuery.withJobsByIdAsync(graphqlClient, workflowRunId, {
useCache: false,
@@ -580,7 +602,14 @@ export default class Go extends EasCommand {
}
if (buildCompleted && submitSpinner === null && submitJob) {
submitSpinner = ora('Submitting to TestFlight').start();
submitStartTime = Date.now();
submitSpinner = ora(
this.formatSpinnerText(
'Submitting to TestFlight',
EXPECTED_SUBMIT_DURATION_SECONDS,
submitStartTime
)
).start();
}
if (workflowRun.status === WorkflowRunStatus.Success) {
@@ -607,4 +636,21 @@ export default class Go extends EasCommand {
await sleepAsync(10 * 1000);
}
}
private formatSpinnerText(
label: string,
expectedDurationSeconds: number,
startTime: number
): string {
const elapsedSeconds = Math.floor((Date.now() - startTime) / 1000);
const remainingSeconds = Math.max(0, expectedDurationSeconds - elapsedSeconds);
if (remainingSeconds === 0) {
return `${label} (almost done...)`;
}
const minutes = Math.ceil(remainingSeconds / 60);
const unit = minutes === 1 ? 'minute' : 'minutes';
return `${label} (~${minutes} ${unit} remaining)`;
}
}
@@ -64,7 +64,7 @@ export class CreateProvisioningProfile {
private async maybeGetUserProvidedAsync(
ctx: CredentialsContext
): Promise<ProvisioningProfile | null> {
if (ctx.nonInteractive) {
if (ctx.nonInteractive || ctx.autoAcceptCredentialReuse) {
return null;
}
const userProvided = await askForUserProvidedAsync(provisioningProfileSchema);
@@ -162,7 +162,7 @@ Remember that Apple Distribution Certificates are not application specific!
export async function provideOrGenerateDistributionCertificateAsync(
ctx: CredentialsContext
): Promise<DistributionCertificate> {
if (!ctx.nonInteractive) {
if (!ctx.nonInteractive && !ctx.autoAcceptCredentialReuse) {
const userProvided = await promptForDistCertAsync(ctx);
if (userProvided) {
if (!ctx.appStore.authCtx) {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "eas-cli-local-build-plugin",
"version": "18.0.2",
"version": "18.0.6",
"description": "Tool for running EAS compatible builds on a local machine.",
"bugs": "https://github.com/expo/eas-cli/issues",
"license": "BUSL-1.1",
@@ -26,7 +26,7 @@
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
},
"dependencies": {
"@expo/build-tools": "18.0.2",
"@expo/build-tools": "18.0.6",
"@expo/eas-build-job": "18.0.2",
"@expo/logger": "18.0.1",
"@expo/spawn-async": "^1.7.2",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@expo/worker",
"version": "18.0.2",
"version": "18.0.6",
"private": true,
"scripts": {
"start": "yarn watch",
@@ -19,7 +19,7 @@
},
"dependencies": {
"@babel/core": "7.24.7",
"@expo/build-tools": "18.0.2",
"@expo/build-tools": "18.0.6",
"@expo/config-plugins": "8.0.8",
"@expo/config-types": "52.0.1",
"@expo/downloader": "18.0.1",
Generated
+4 -3
View File
@@ -2020,7 +2020,7 @@ __metadata:
languageName: node
linkType: hard
"@expo/build-tools@npm:18.0.2, @expo/build-tools@workspace:packages/build-tools":
"@expo/build-tools@npm:18.0.6, @expo/build-tools@workspace:packages/build-tools":
version: 0.0.0-use.local
resolution: "@expo/build-tools@workspace:packages/build-tools"
dependencies:
@@ -2055,6 +2055,7 @@ __metadata:
bplist-parser: "npm:0.3.2"
bunyan: "npm:^1.8.15"
fast-glob: "npm:^3.3.2"
fast-xml-parser: "npm:^4.4.1"
fs-extra: "npm:^11.2.0"
gql.tada: "npm:^1.8.13"
jest: "npm:^29.7.0"
@@ -2717,7 +2718,7 @@ __metadata:
resolution: "@expo/worker@workspace:packages/worker"
dependencies:
"@babel/core": "npm:7.24.7"
"@expo/build-tools": "npm:18.0.2"
"@expo/build-tools": "npm:18.0.6"
"@expo/config-plugins": "npm:8.0.8"
"@expo/config-types": "npm:52.0.1"
"@expo/downloader": "npm:18.0.1"
@@ -10120,7 +10121,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "eas-cli-local-build-plugin@workspace:packages/local-build-plugin"
dependencies:
"@expo/build-tools": "npm:18.0.2"
"@expo/build-tools": "npm:18.0.6"
"@expo/eas-build-job": "npm:18.0.2"
"@expo/logger": "npm:18.0.1"
"@expo/spawn-async": "npm:^1.7.2"