Skip to content

Commit 6ae19df

Browse files
[eas-cli] Add build:download command (#2982)
* [eas-cli] Add build:download command * Fix json flag * Address PR comments * Add dev-client flag
1 parent eedc841 commit 6ae19df

File tree

3 files changed

+188
-0
lines changed

3 files changed

+188
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ This is the log of notable changes to EAS CLI and related packages.
88

99
### 🎉 New features
1010

11+
- Add `eas build:download` command. ([#2982](https://github.com/expo/eas-cli/pull/2982) by [@gabrieldonadel](https://github.com/gabrieldonadel))
12+
1113
### 🐛 Bug fixes
1214

1315
### 🧹 Chores

packages/eas-cli/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ eas --help COMMAND
7070
* [`eas build:configure`](#eas-buildconfigure)
7171
* [`eas build:delete [BUILD_ID]`](#eas-builddelete-build_id)
7272
* [`eas build:dev`](#eas-builddev)
73+
* [`eas build:download`](#eas-builddownload)
7374
* [`eas build:inspect`](#eas-buildinspect)
7475
* [`eas build:list`](#eas-buildlist)
7576
* [`eas build:resign`](#eas-buildresign)
@@ -459,6 +460,26 @@ DESCRIPTION
459460

460461
_See code: [packages/eas-cli/src/commands/build/dev.ts](https://github.com/expo/eas-cli/blob/v16.2.2/packages/eas-cli/src/commands/build/dev.ts)_
461462

463+
## `eas build:download`
464+
465+
download a simulator/emulator build with matching fingerprint
466+
467+
```
468+
USAGE
469+
$ eas build:download [-p ios|android] [-e <value>]
470+
471+
FLAGS
472+
--dev-client=<value> Filter only dev-client builds.
473+
--fingerprint=<value> (required) Fingerprint hash of the build to run.
474+
--non-interactive Run the command in non-interactive mode.
475+
-p, --platform=(ios|android)
476+
477+
DESCRIPTION
478+
download a simulator/emulator build with matching fingerprint
479+
```
480+
481+
_See code: [packages/eas-cli/src/commands/build/download.ts](https://github.com/expo/eas-cli/blob/v16.2.2/packages/eas-cli/src/commands/build/download.ts)_
482+
462483
## `eas build:inspect`
463484

464485
inspect the state of the project at specific build stages, useful for troubleshooting
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import { Platform } from '@expo/eas-build-job';
2+
import { Errors, Flags } from '@oclif/core';
3+
import chalk from 'chalk';
4+
import { pathExists } from 'fs-extra';
5+
6+
import EasCommand from '../../commandUtils/EasCommand';
7+
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
8+
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
9+
import { AppPlatform, BuildFragment, BuildStatus, DistributionType } from '../../graphql/generated';
10+
import { BuildQuery } from '../../graphql/queries/BuildQuery';
11+
import { toAppPlatform } from '../../graphql/types/AppPlatform';
12+
import Log from '../../log';
13+
import { promptAsync } from '../../prompts';
14+
import { getEasBuildRunCachedAppPath } from '../../run/run';
15+
import { downloadAndMaybeExtractAppAsync } from '../../utils/download';
16+
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
17+
18+
export default class Download extends EasCommand {
19+
static override description = 'download simulator/emulator builds for a given fingerprint hash';
20+
21+
static override flags = {
22+
fingerprint: Flags.string({
23+
description: 'Fingerprint hash of the build to download',
24+
required: true,
25+
}),
26+
platform: Flags.enum<Platform.IOS | Platform.ANDROID>({
27+
char: 'p',
28+
options: [Platform.IOS, Platform.ANDROID],
29+
}),
30+
'dev-client': Flags.boolean({
31+
description: 'Filter only dev-client builds.',
32+
}),
33+
...EasNonInteractiveAndJsonFlags,
34+
};
35+
36+
static override contextDefinition = {
37+
...this.ContextOptions.LoggedIn,
38+
...this.ContextOptions.ProjectId,
39+
};
40+
41+
async runAsync(): Promise<void> {
42+
const {
43+
flags: {
44+
json: jsonFlag,
45+
platform,
46+
fingerprint,
47+
'dev-client': developmentClient,
48+
'non-interactive': nonInteractive,
49+
},
50+
} = await this.parse(Download);
51+
52+
const {
53+
loggedIn: { graphqlClient },
54+
projectId,
55+
} = await this.getContextAsync(Download, {
56+
nonInteractive,
57+
});
58+
59+
if (jsonFlag) {
60+
enableJsonOutput();
61+
}
62+
63+
const selectedPlatform = await resolvePlatformAsync({ nonInteractive, platform });
64+
const build = await this.getBuildAsync({
65+
graphqlClient,
66+
projectId,
67+
platform: selectedPlatform,
68+
fingerprintHash: fingerprint,
69+
developmentClient,
70+
});
71+
const buildArtifactPath = await this.getPathToBuildArtifactAsync(build, selectedPlatform);
72+
if (jsonFlag) {
73+
const jsonResults = { path: buildArtifactPath };
74+
printJsonOnlyOutput(jsonResults);
75+
} else {
76+
Log.log(`Build downloaded to ${chalk.bold(buildArtifactPath)}`);
77+
}
78+
}
79+
80+
private async getBuildAsync({
81+
graphqlClient,
82+
projectId,
83+
platform,
84+
fingerprintHash,
85+
developmentClient,
86+
}: {
87+
graphqlClient: ExpoGraphqlClient;
88+
projectId: string;
89+
platform: AppPlatform;
90+
fingerprintHash: string;
91+
developmentClient: boolean;
92+
}): Promise<BuildFragment> {
93+
const builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
94+
appId: projectId,
95+
filter: {
96+
platform,
97+
fingerprintHash,
98+
status: BuildStatus.Finished,
99+
simulator: platform === AppPlatform.Ios ? true : undefined,
100+
distribution: platform === AppPlatform.Android ? DistributionType.Internal : undefined,
101+
developmentClient,
102+
},
103+
offset: 0,
104+
limit: 1,
105+
});
106+
if (builds.length === 0) {
107+
throw new Errors.CLIError(
108+
`No builds available for ${platform} with fingerprint hash ${fingerprintHash}`
109+
);
110+
}
111+
112+
Log.succeed(`🎯 Found successful build with matching fingerprint on EAS servers.`);
113+
return builds[0];
114+
}
115+
116+
async getPathToBuildArtifactAsync(build: BuildFragment, platform: AppPlatform): Promise<string> {
117+
const cachedBuildArtifactPath = getEasBuildRunCachedAppPath(
118+
build.project.id,
119+
build.id,
120+
platform
121+
);
122+
if (await pathExists(cachedBuildArtifactPath)) {
123+
Log.newLine();
124+
Log.log(`Using cached build...`);
125+
return cachedBuildArtifactPath;
126+
}
127+
128+
if (!build.artifacts?.applicationArchiveUrl) {
129+
throw new Error('Build does not have an application archive url');
130+
}
131+
132+
return await downloadAndMaybeExtractAppAsync(
133+
build.artifacts.applicationArchiveUrl,
134+
platform,
135+
cachedBuildArtifactPath
136+
);
137+
}
138+
}
139+
140+
async function resolvePlatformAsync({
141+
nonInteractive,
142+
platform,
143+
}: {
144+
nonInteractive: boolean;
145+
platform?: Platform;
146+
}): Promise<AppPlatform> {
147+
if (nonInteractive && !platform) {
148+
throw new Error('Platform must be provided in non-interactive mode');
149+
}
150+
151+
if (platform) {
152+
return toAppPlatform(platform);
153+
}
154+
155+
const { selectedPlatform } = await promptAsync({
156+
type: 'select',
157+
message: 'Select platform',
158+
name: 'selectedPlatform',
159+
choices: [
160+
{ title: 'Android', value: AppPlatform.Android },
161+
{ title: 'iOS', value: AppPlatform.Ios },
162+
],
163+
});
164+
return selectedPlatform;
165+
}

0 commit comments

Comments
 (0)