eas-cli 14.7.1 → 15.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@ import { LocalBuildOptions } from './local';
5
5
  import { Analytics } from '../analytics/AnalyticsManager';
6
6
  import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectConfigContextField';
7
7
  import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
8
+ import { BuildFragment } from '../graphql/generated';
8
9
  import { RequestedPlatform } from '../platform';
9
10
  import { Actor } from '../user/User';
10
11
  import { Client } from '../vcs/vcs';
@@ -37,3 +38,4 @@ export declare function runBuildAndSubmitAsync({ graphqlClient, analytics, vcsCl
37
38
  }): Promise<{
38
39
  buildIds: string[];
39
40
  }>;
41
+ export declare function downloadAndRunAsync(build: BuildFragment): Promise<void>;
@@ -1,12 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.runBuildAndSubmitAsync = void 0;
3
+ exports.downloadAndRunAsync = exports.runBuildAndSubmitAsync = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const eas_build_job_1 = require("@expo/eas-build-job");
6
6
  const eas_json_1 = require("@expo/eas-json");
7
7
  const logger_1 = require("@expo/logger");
8
8
  const assert_1 = tslib_1.__importDefault(require("assert"));
9
9
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
10
+ const fs_extra_1 = require("fs-extra");
10
11
  const nullthrows_1 = tslib_1.__importDefault(require("nullthrows"));
11
12
  const build_1 = require("./android/build");
12
13
  const build_2 = require("./build");
@@ -340,9 +341,17 @@ function exitWithNonZeroCodeIfSomeBuildsFailed(maybeBuilds) {
340
341
  }
341
342
  async function downloadAndRunAsync(build) {
342
343
  (0, assert_1.default)(build.artifacts?.applicationArchiveUrl);
343
- const buildPath = await (0, download_1.downloadAndMaybeExtractAppAsync)(build.artifacts.applicationArchiveUrl, build.platform);
344
+ const cachedAppPath = (0, run_1.getEasBuildRunCachedAppPath)(build.project.id, build.id, build.platform);
345
+ if (await (0, fs_extra_1.pathExists)(cachedAppPath)) {
346
+ log_1.default.newLine();
347
+ log_1.default.log(`Using cached app...`);
348
+ await (0, run_1.runAsync)(cachedAppPath, build.platform);
349
+ return;
350
+ }
351
+ const buildPath = await (0, download_1.downloadAndMaybeExtractAppAsync)(build.artifacts.applicationArchiveUrl, build.platform, cachedAppPath);
344
352
  await (0, run_1.runAsync)(buildPath, build.platform);
345
353
  }
354
+ exports.downloadAndRunAsync = downloadAndRunAsync;
346
355
  async function maybeDownloadAndRunSimulatorBuildsAsync(builds, flags, autoConfirm) {
347
356
  const simBuilds = builds.filter(filter_1.truthy).filter(utils_1.isRunnableOnSimulatorOrEmulator);
348
357
  if (simBuilds.length > 0 && !flags.autoSubmit && !flags.nonInteractive) {
@@ -20,4 +20,5 @@ export default class BuildDev extends EasCommand {
20
20
  private selectPlatformAsync;
21
21
  private validateBuildRunProfileAsync;
22
22
  private ensureValidBuildRunProfileExistsAsync;
23
+ private getBuildsAsync;
23
24
  }
@@ -16,8 +16,6 @@ const log_1 = tslib_1.__importDefault(require("../../log"));
16
16
  const platform_1 = require("../../platform");
17
17
  const workflow_1 = require("../../project/workflow");
18
18
  const prompts_1 = require("../../prompts");
19
- const run_1 = require("../../run/run");
20
- const download_1 = require("../../utils/download");
21
19
  const fingerprintCli_1 = require("../../utils/fingerprintCli");
22
20
  const profiles_1 = require("../../utils/profiles");
23
21
  const DEFAULT_EAS_BUILD_RUN_PROFILE_NAME = 'development-simulator';
@@ -84,25 +82,17 @@ class BuildDev extends EasCommand_1.default {
84
82
  }
85
83
  log_1.default.log(`✨ Calculated fingerprint hash: ${fingerprint.hash}`);
86
84
  log_1.default.newLine();
87
- const builds = await BuildQuery_1.BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
88
- appId: projectId,
89
- filter: {
90
- platform: (0, AppPlatform_1.toAppPlatform)(platform),
91
- fingerprintHash: fingerprint.hash,
92
- status: generated_1.BuildStatus.Finished,
93
- simulator: platform === eas_build_job_1.Platform.IOS ? true : undefined,
94
- distribution: platform === eas_build_job_1.Platform.ANDROID ? generated_1.DistributionType.Internal : undefined,
95
- developmentClient: true,
96
- },
97
- offset: 0,
98
- limit: 1,
85
+ const builds = await this.getBuildsAsync({
86
+ graphqlClient,
87
+ projectId,
88
+ platform,
89
+ fingerprint,
99
90
  });
100
91
  if (builds.length !== 0) {
101
92
  const build = builds[0];
102
93
  log_1.default.succeed(`🎯 Found successful build with matching fingerprint on EAS servers. Running it...`);
103
94
  if (build.artifacts?.applicationArchiveUrl) {
104
- const buildPath = await (0, download_1.downloadAndMaybeExtractAppAsync)(build.artifacts.applicationArchiveUrl, build.platform);
105
- await (0, run_1.runAsync)(buildPath, build.platform);
95
+ await (0, runBuildAndSubmit_1.downloadAndRunAsync)(build);
106
96
  return;
107
97
  }
108
98
  else {
@@ -110,6 +100,15 @@ class BuildDev extends EasCommand_1.default {
110
100
  }
111
101
  }
112
102
  log_1.default.log('🚀 No successful build with matching fingerprint found. Starting a new build...');
103
+ const previousBuildsForSelectedProfile = await this.getBuildsAsync({
104
+ graphqlClient,
105
+ projectId,
106
+ platform,
107
+ });
108
+ if (previousBuildsForSelectedProfile.length > 0 &&
109
+ previousBuildsForSelectedProfile[0].metrics?.buildDuration) {
110
+ log_1.default.log(`🕒 Previous build for "${buildProfile.profileName}" profile completed in ${Math.floor(previousBuildsForSelectedProfile[0].metrics.buildDuration / 60000)} minutes.`);
111
+ }
113
112
  await (0, runBuildAndSubmit_1.runBuildAndSubmitAsync)({
114
113
  graphqlClient,
115
114
  analytics,
@@ -221,5 +220,20 @@ class BuildDev extends EasCommand_1.default {
221
220
  });
222
221
  return buildProfile;
223
222
  }
223
+ async getBuildsAsync({ graphqlClient, projectId, platform, fingerprint, }) {
224
+ return await BuildQuery_1.BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
225
+ appId: projectId,
226
+ filter: {
227
+ platform: (0, AppPlatform_1.toAppPlatform)(platform),
228
+ fingerprintHash: fingerprint?.hash,
229
+ status: generated_1.BuildStatus.Finished,
230
+ simulator: platform === eas_build_job_1.Platform.IOS ? true : undefined,
231
+ distribution: platform === eas_build_job_1.Platform.ANDROID ? generated_1.DistributionType.Internal : undefined,
232
+ developmentClient: true,
233
+ },
234
+ offset: 0,
235
+ limit: 1,
236
+ });
237
+ }
224
238
  }
225
239
  exports.default = BuildDev;
@@ -7,8 +7,7 @@ const local_1 = require("../../build/local");
7
7
  const runBuildAndSubmit_1 = require("../../build/runBuildAndSubmit");
8
8
  const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
9
9
  const json_1 = require("../../utils/json");
10
- const gitNoCommit_1 = tslib_1.__importDefault(require("../../vcs/clients/gitNoCommit"));
11
- const noVcs_1 = tslib_1.__importDefault(require("../../vcs/clients/noVcs"));
10
+ const git_1 = tslib_1.__importDefault(require("../../vcs/clients/git"));
12
11
  /**
13
12
  * This command will be run on the EAS Build workers, when building
14
13
  * directly from git. This command resolves credentials and other
@@ -52,9 +51,14 @@ class BuildInternal extends EasCommand_1.default {
52
51
  (0, json_1.enableJsonOutput)();
53
52
  const { loggedIn: { actor, graphqlClient }, getDynamicPrivateProjectConfigAsync, projectDir, analytics, vcsClient, } = await this.getContextAsync(BuildInternal, {
54
53
  nonInteractive: true,
55
- vcsClientOverride: process.env.EAS_NO_VCS ? new noVcs_1.default() : new gitNoCommit_1.default(),
56
54
  withServerSideEnvironment: null,
57
55
  });
56
+ if (vcsClient instanceof git_1.default) {
57
+ // `build:internal` is run on EAS workers and the repo may have been changed
58
+ // by pre-install hooks or other scripts. We don't want to require committing changes
59
+ // to continue the build.
60
+ vcsClient.requireCommit = false;
61
+ }
58
62
  await (0, _1.handleDeprecatedEasJsonAsync)(projectDir, flags.nonInteractive);
59
63
  await (0, runBuildAndSubmit_1.runBuildAndSubmitAsync)({
60
64
  graphqlClient,
@@ -4,7 +4,6 @@ const tslib_1 = require("tslib");
4
4
  const core_1 = require("@oclif/core");
5
5
  const assert_1 = tslib_1.__importDefault(require("assert"));
6
6
  const fs_extra_1 = require("fs-extra");
7
- const path_1 = tslib_1.__importDefault(require("path"));
8
7
  const queries_1 = require("../../build/queries");
9
8
  const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
10
9
  const pagination_1 = require("../../commandUtils/pagination");
@@ -17,7 +16,6 @@ const prompts_1 = require("../../prompts");
17
16
  const run_1 = require("../../run/run");
18
17
  const utils_1 = require("../../run/utils");
19
18
  const download_1 = require("../../utils/download");
20
- const paths_1 = require("../../utils/paths");
21
19
  class Run extends EasCommand_1.default {
22
20
  static description = 'run simulator/emulator builds from eas-cli';
23
21
  static flags = {
@@ -167,13 +165,10 @@ async function maybeGetBuildAsync(graphqlClient, flags, projectId, paginatedQuer
167
165
  return null;
168
166
  }
169
167
  }
170
- function getEasBuildRunCachedAppPath(projectId, buildId, platform) {
171
- return path_1.default.join((0, paths_1.getEasBuildRunCacheDirectoryPath)(), `${projectId}_${buildId}.${platform === generated_1.AppPlatform.Ios ? 'app' : 'apk'}`);
172
- }
173
168
  async function getPathToSimulatorBuildAppAsync(graphqlClient, projectId, flags, queryOptions) {
174
169
  const maybeBuild = await maybeGetBuildAsync(graphqlClient, flags, projectId, queryOptions);
175
170
  if (maybeBuild) {
176
- const cachedAppPath = getEasBuildRunCachedAppPath(projectId, maybeBuild.id, flags.selectedPlatform);
171
+ const cachedAppPath = (0, run_1.getEasBuildRunCachedAppPath)(projectId, maybeBuild.id, flags.selectedPlatform);
177
172
  if (await (0, fs_extra_1.pathExists)(cachedAppPath)) {
178
173
  log_1.default.newLine();
179
174
  log_1.default.log(`Using cached app...`);
@@ -2,13 +2,6 @@ import EasCommand from '../../commandUtils/EasCommand';
2
2
  import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
3
3
  import { BuildStatus } from '../../graphql/generated';
4
4
  import { RequestedPlatform } from '../../platform';
5
- export interface FingerprintCompareFlags {
6
- buildId?: string;
7
- hash1?: string;
8
- hash2?: string;
9
- nonInteractive: boolean;
10
- json: boolean;
11
- }
12
5
  export default class FingerprintCompare extends EasCommand {
13
6
  static description: string;
14
7
  static strict: boolean;
@@ -21,7 +14,8 @@ export default class FingerprintCompare extends EasCommand {
21
14
  static flags: {
22
15
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
23
16
  'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
24
- 'build-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined>;
17
+ 'build-id': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined>;
18
+ 'update-id': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined>;
25
19
  };
26
20
  static contextDefinition: {
27
21
  vcsClient: import("../../commandUtils/context/VcsClientContextField").default;
@@ -5,19 +5,24 @@ const tslib_1 = require("tslib");
5
5
  const eas_build_job_1 = require("@expo/eas-build-job");
6
6
  const core_1 = require("@oclif/core");
7
7
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
8
+ const api_1 = require("../../api");
9
+ const queries_1 = require("../../branch/queries");
8
10
  const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
9
11
  const builds_1 = require("../../commandUtils/builds");
10
12
  const flags_1 = require("../../commandUtils/flags");
11
13
  const generated_1 = require("../../graphql/generated");
12
14
  const FingerprintMutation_1 = require("../../graphql/mutations/FingerprintMutation");
15
+ const AppQuery_1 = require("../../graphql/queries/AppQuery");
13
16
  const BuildQuery_1 = require("../../graphql/queries/BuildQuery");
14
17
  const FingerprintQuery_1 = require("../../graphql/queries/FingerprintQuery");
18
+ const UpdateQuery_1 = require("../../graphql/queries/UpdateQuery");
15
19
  const log_1 = tslib_1.__importDefault(require("../../log"));
16
20
  const ora_1 = require("../../ora");
17
21
  const maybeUploadFingerprintAsync_1 = require("../../project/maybeUploadFingerprintAsync");
18
22
  const projectUtils_1 = require("../../project/projectUtils");
19
23
  const workflow_1 = require("../../project/workflow");
20
24
  const prompts_1 = require("../../prompts");
25
+ const queries_2 = require("../../update/queries");
21
26
  const fingerprintCli_1 = require("../../utils/fingerprintCli");
22
27
  const fingerprintDiff_1 = require("../../utils/fingerprintDiff");
23
28
  const formatFields_1 = tslib_1.__importDefault(require("../../utils/formatFields"));
@@ -25,6 +30,7 @@ const json_1 = require("../../utils/json");
25
30
  var FingerprintOriginType;
26
31
  (function (FingerprintOriginType) {
27
32
  FingerprintOriginType["Build"] = "build";
33
+ FingerprintOriginType["Update"] = "update";
28
34
  FingerprintOriginType["Hash"] = "hash";
29
35
  FingerprintOriginType["Project"] = "project";
30
36
  })(FingerprintOriginType || (FingerprintOriginType = {}));
@@ -33,9 +39,12 @@ class FingerprintCompare extends EasCommand_1.default {
33
39
  static strict = false;
34
40
  static examples = [
35
41
  '$ eas fingerprint:compare \t # Compare fingerprints in interactive mode',
36
- '$ eas fingerprint:compare c71a7d475aa6f75291bc93cd74aef395c3c94eee \t # Compare fingerprint against local directory',
37
- '$ eas fingerprint:compare c71a7d475aa6f75291bc93cd74aef395c3c94eee f0d6a916e73f401d428e6e006e07b12453317ba2 \t # Compare provided fingerprints',
38
- '$ eas fingerprint:compare --build-id 82bc6456-611a-48cb-8db4-5f9eb2ca1003 \t # Compare fingerprint from build against local directory',
42
+ '$ eas fingerprint:compare <FINGERPRINT-HASH> \t # Compare fingerprint against local directory',
43
+ '$ eas fingerprint:compare <FINGERPRINT-HASH-1> <FINGERPRINT-HASH-2> \t # Compare provided fingerprints',
44
+ '$ eas fingerprint:compare --build-id <BUILD-ID> \t # Compare fingerprint from build against local directory',
45
+ '$ eas fingerprint:compare --build-id <BUILD-ID-1> --build-id <BUILD-ID-2>\t # Compare fingerprint from a build against another build',
46
+ '$ eas fingerprint:compare --build-id <BUILD-ID> --update-id <UPDATE-ID>\t # Compare fingerprint from build against fingerprint from update',
47
+ '$ eas fingerprint:compare <FINGERPRINT-HASH> --update-id <UPDATE-ID> \t # Compare fingerprint from update against provided fingerprint',
39
48
  ];
40
49
  static args = [
41
50
  {
@@ -53,6 +62,12 @@ class FingerprintCompare extends EasCommand_1.default {
53
62
  'build-id': core_1.Flags.string({
54
63
  aliases: ['buildId'],
55
64
  description: 'Compare the fingerprint with the build with the specified ID',
65
+ multiple: true,
66
+ }),
67
+ 'update-id': core_1.Flags.string({
68
+ aliases: ['updateId'],
69
+ description: 'Compare the fingerprint with the update with the specified ID',
70
+ multiple: true,
56
71
  }),
57
72
  ...flags_1.EasNonInteractiveAndJsonFlags,
58
73
  };
@@ -65,8 +80,9 @@ class FingerprintCompare extends EasCommand_1.default {
65
80
  async runAsync() {
66
81
  const { args, flags } = await this.parse(FingerprintCompare);
67
82
  const { hash1, hash2 } = args;
68
- const { json, 'non-interactive': nonInteractive, 'build-id': buildId } = flags;
69
- const sanitizedFlagsAndArgs = { json, nonInteractive, buildId, hash1, hash2 };
83
+ const { json, 'non-interactive': nonInteractive, 'build-id': buildIds, 'update-id': updateIds, } = flags;
84
+ const [buildId1, buildId2] = buildIds ?? [];
85
+ const [updateId1, updateId2] = updateIds ?? [];
70
86
  const { projectId, privateProjectConfig: { projectDir }, loggedIn: { graphqlClient }, vcsClient, } = await this.getContextAsync(FingerprintCompare, {
71
87
  nonInteractive,
72
88
  withServerSideEnvironment: null,
@@ -74,16 +90,29 @@ class FingerprintCompare extends EasCommand_1.default {
74
90
  if (json) {
75
91
  (0, json_1.enableJsonOutput)();
76
92
  }
77
- const firstFingerprintInfo = await getFirstFingerprintInfoAsync(graphqlClient, projectId, sanitizedFlagsAndArgs);
93
+ const firstFingerprintInfo = await getFingerprintInfoAsync(graphqlClient, projectDir, projectId, vcsClient, {
94
+ nonInteractive,
95
+ buildId: buildId1,
96
+ updateId: updateId1,
97
+ hash: hash1,
98
+ });
78
99
  const { fingerprint: firstFingerprint, origin: firstFingerprintOrigin } = firstFingerprintInfo;
79
- const secondFingerprintInfo = await getSecondFingerprintInfoAsync(graphqlClient, projectDir, projectId, vcsClient, firstFingerprintInfo, sanitizedFlagsAndArgs);
100
+ const isFirstFingerprintSpecifiedByFlagOrArg = hash1 || buildId1 || updateId1;
101
+ const isSecondFingerprintSpecifiedByFlagOrArg = hash2 || buildId2 || updateId2;
102
+ const secondFingerprintInfo = await getFingerprintInfoAsync(graphqlClient, projectDir, projectId, vcsClient, {
103
+ nonInteractive,
104
+ buildId: buildId2,
105
+ updateId: updateId2,
106
+ hash: hash2,
107
+ useProjectFingerprint: isFirstFingerprintSpecifiedByFlagOrArg && !isSecondFingerprintSpecifiedByFlagOrArg,
108
+ }, firstFingerprintInfo);
80
109
  const { fingerprint: secondFingerprint, origin: secondFingerprintOrigin } = secondFingerprintInfo;
81
110
  if (json) {
82
111
  (0, json_1.printJsonOnlyOutput)({ fingerprint1: firstFingerprint, fingerprint2: secondFingerprint });
83
112
  return;
84
113
  }
85
114
  if (firstFingerprint.hash === secondFingerprint.hash) {
86
- log_1.default.log(`✅ ${capitalizeFirstLetter(prettyPrintFingerprint(firstFingerprint, firstFingerprintOrigin))} matches fingerprint from ${prettyPrintFingerprint(secondFingerprint, secondFingerprintOrigin)}`);
115
+ log_1.default.log(`✅ ${capitalizeFirstLetter(prettyPrintFingerprint(firstFingerprint, firstFingerprintOrigin))} matches ${prettyPrintFingerprint(secondFingerprint, secondFingerprintOrigin)}`);
87
116
  return;
88
117
  }
89
118
  else {
@@ -141,77 +170,95 @@ class FingerprintCompare extends EasCommand_1.default {
141
170
  }
142
171
  }
143
172
  exports.default = FingerprintCompare;
144
- function prettyPrintFingerprint(fingerprint, origin) {
145
- if (origin.type === FingerprintOriginType.Hash) {
146
- return `fingerprint ${fingerprint.hash} from hash`;
173
+ async function getFingerprintInfoAsync(graphqlClient, projectDir, projectId, vcsClient, { buildId, updateId, hash, useProjectFingerprint, nonInteractive, }, firstFingerprintInfo) {
174
+ if (hash) {
175
+ return await getFingerprintInfoFromHashAsync(graphqlClient, projectId, hash);
147
176
  }
148
- return `fingerprint ${fingerprint.hash} from ${origin.type}`;
149
- }
150
- function capitalizeFirstLetter(string) {
151
- return string.charAt(0).toUpperCase() + string.slice(1);
152
- }
153
- async function getFirstFingerprintInfoAsync(graphqlClient, projectId, { buildId: buildIdFromArg, hash1, nonInteractive }) {
154
- if (hash1) {
155
- const fingerprintFragment = await getFingerprintFragmentFromHashAsync(graphqlClient, projectId, hash1);
156
- const fingerprint = await getFingerprintFromFingerprintFragmentAsync(fingerprintFragment);
157
- let platforms;
158
- const fingerprintBuilds = fingerprintFragment.builds?.edges.map(edge => edge.node) ?? [];
159
- const fingerprintUpdates = fingerprintFragment.updates?.edges.map(edge => edge.node) ?? [];
160
- if (fingerprintBuilds.length > 0) {
161
- platforms = [fingerprintBuilds[0].platform];
162
- }
163
- else if (fingerprintUpdates.length > 0) {
164
- platforms = [stringToAppPlatform(fingerprintUpdates[0].platform)];
177
+ else if (updateId) {
178
+ return await getFingerprintInfoFromUpdateGroupIdOrUpdateIdAsync(graphqlClient, projectId, nonInteractive, updateId);
179
+ }
180
+ else if (buildId) {
181
+ return await getFingerprintInfoFromBuildIdAsync(graphqlClient, buildId);
182
+ }
183
+ else if (useProjectFingerprint) {
184
+ if (!firstFingerprintInfo) {
185
+ throw new Error('First fingerprint must be provided in order to compare against the project.');
165
186
  }
166
- return {
167
- fingerprint,
168
- platforms,
169
- origin: {
170
- type: FingerprintOriginType.Hash,
171
- },
172
- };
187
+ return await getFingerprintInfoFromLocalProjectAsync(graphqlClient, projectDir, projectId, vcsClient, firstFingerprintInfo);
173
188
  }
174
- let buildId = buildIdFromArg ?? null;
175
- if (!buildId) {
176
- if (nonInteractive) {
177
- throw new Error('Build ID must be provided in non-interactive mode');
189
+ if (nonInteractive) {
190
+ throw new Error('Insufficent arguments provided for fingerprint comparison in non-interactive mode');
191
+ }
192
+ return await getFingerprintInfoInteractiveAsync(graphqlClient, projectDir, projectId, vcsClient, firstFingerprintInfo);
193
+ }
194
+ async function getFingerprintInfoInteractiveAsync(graphqlClient, projectDir, projectId, vcsClient, firstFingerprintInfo) {
195
+ const prompt = firstFingerprintInfo
196
+ ? 'Select the second fingerprint to compare against'
197
+ : 'Select a reference fingerprint for comparison';
198
+ const originType = await (0, prompts_1.selectAsync)(prompt, [
199
+ ...(firstFingerprintInfo
200
+ ? [{ title: 'Current project fingerprint', value: FingerprintOriginType.Project }]
201
+ : []),
202
+ { title: 'Build fingerprint', value: FingerprintOriginType.Build },
203
+ { title: 'Update fingerprint', value: FingerprintOriginType.Update },
204
+ { title: 'Enter a fingerprint hash manually', value: FingerprintOriginType.Hash },
205
+ ]);
206
+ if (originType === FingerprintOriginType.Project) {
207
+ if (!firstFingerprintInfo) {
208
+ throw new Error('First fingerprint must be provided in order to compare against the project.');
178
209
  }
210
+ return await getFingerprintInfoFromLocalProjectAsync(graphqlClient, projectDir, projectId, vcsClient, firstFingerprintInfo);
211
+ }
212
+ else if (originType === FingerprintOriginType.Build) {
179
213
  const displayName = await (0, projectUtils_1.getDisplayNameForProjectIdAsync)(graphqlClient, projectId);
180
- buildId = await selectBuildToCompareAsync(graphqlClient, projectId, displayName, {
214
+ const buildId = await selectBuildToCompareAsync(graphqlClient, projectId, displayName, {
181
215
  filters: { hasFingerprint: true },
182
216
  });
183
217
  if (!buildId) {
184
218
  throw new Error('Must select build with fingerprint for comparison.');
185
219
  }
220
+ return await getFingerprintInfoFromBuildIdAsync(graphqlClient, buildId);
221
+ }
222
+ else if (originType === FingerprintOriginType.Update) {
223
+ const selectedBranch = await (0, queries_1.selectBranchOnAppAsync)(graphqlClient, {
224
+ projectId,
225
+ promptTitle: 'On which branch would you like search for an update?',
226
+ displayTextForListItem: updateBranch => ({
227
+ title: updateBranch.name,
228
+ }),
229
+ paginatedQueryOptions: {
230
+ json: false,
231
+ nonInteractive: false,
232
+ offset: 0,
233
+ },
234
+ });
235
+ const selectedUpdateGroup = await (0, queries_2.selectUpdateGroupOnBranchAsync)(graphqlClient, {
236
+ projectId,
237
+ branchName: selectedBranch.name,
238
+ paginatedQueryOptions: {
239
+ json: false,
240
+ nonInteractive: false,
241
+ offset: 0,
242
+ },
243
+ });
244
+ const updateGroupId = selectedUpdateGroup[0].group;
245
+ return await getFingerprintInfoFromUpdateGroupIdOrUpdateIdAsync(graphqlClient, projectId, false, updateGroupId);
246
+ }
247
+ else if (originType === FingerprintOriginType.Hash) {
248
+ const { hash } = await (0, prompts_1.promptAsync)({
249
+ type: 'text',
250
+ name: 'hash',
251
+ message: 'Provide the fingerprint hash',
252
+ validate: (value) => !!value.trim(),
253
+ hint: '0000000000000000000000000000000000000000',
254
+ });
255
+ return await getFingerprintInfoFromHashAsync(graphqlClient, projectId, hash);
186
256
  }
187
- log_1.default.log(`Comparing fingerprints of the current project and build ${buildId}…`);
188
- const buildWithFingerprint = await BuildQuery_1.BuildQuery.withFingerprintByIdAsync(graphqlClient, buildId);
189
- if (!buildWithFingerprint.fingerprint) {
190
- throw new Error(`Fingerprint for build ${buildId} was not computed.`);
191
- }
192
- else if (!buildWithFingerprint.fingerprint.debugInfoUrl) {
193
- throw new Error(`Fingerprint source for build ${buildId} was not computed.`);
257
+ else {
258
+ throw new Error(`Unsupported fingerprint origin type: ${originType}`);
194
259
  }
195
- return {
196
- fingerprint: await getFingerprintFromFingerprintFragmentAsync(buildWithFingerprint.fingerprint),
197
- platforms: [buildWithFingerprint.platform],
198
- origin: {
199
- type: FingerprintOriginType.Build,
200
- build: buildWithFingerprint,
201
- },
202
- };
203
260
  }
204
- async function getSecondFingerprintInfoAsync(graphqlClient, projectDir, projectId, vcsClient, firstFingerprintInfo, { hash2 }) {
205
- if (hash2) {
206
- const fingerprintFragment = await getFingerprintFragmentFromHashAsync(graphqlClient, projectId, hash2);
207
- if (!fingerprintFragment) {
208
- throw new Error(`Fingerprint with hash ${hash2} was not uploaded.`);
209
- }
210
- return {
211
- fingerprint: await getFingerprintFromFingerprintFragmentAsync(fingerprintFragment),
212
- origin: { type: FingerprintOriginType.Hash },
213
- };
214
- }
261
+ async function getFingerprintInfoFromLocalProjectAsync(graphqlClient, projectDir, projectId, vcsClient, firstFingerprintInfo) {
215
262
  const firstFingerprintPlatforms = firstFingerprintInfo.platforms;
216
263
  if (!firstFingerprintPlatforms) {
217
264
  throw new Error(`Cannot compare the local directory against the provided fingerprint hash "${firstFingerprintInfo.fingerprint.hash}" because the associated platform could not be determined. Ensure the fingerprint is linked to a build or update to identify the platform.`);
@@ -241,6 +288,96 @@ async function getSecondFingerprintInfoAsync(graphqlClient, projectDir, projectI
241
288
  });
242
289
  return { fingerprint: projectFingerprint, origin: { type: FingerprintOriginType.Project } };
243
290
  }
291
+ async function getFingerprintFromUpdateFragmentAsync(updateWithFingerprint) {
292
+ if (!updateWithFingerprint.fingerprint) {
293
+ throw new Error(`Fingerprint for update ${updateWithFingerprint.id} was not computed.`);
294
+ }
295
+ else if (!updateWithFingerprint.fingerprint.debugInfoUrl) {
296
+ throw new Error(`Fingerprint source for update ${updateWithFingerprint.id} was not computed.`);
297
+ }
298
+ return {
299
+ fingerprint: await getFingerprintFromFingerprintFragmentAsync(updateWithFingerprint.fingerprint),
300
+ platforms: [stringToAppPlatform(updateWithFingerprint.platform)],
301
+ origin: {
302
+ type: FingerprintOriginType.Update,
303
+ update: updateWithFingerprint,
304
+ },
305
+ };
306
+ }
307
+ async function getFingerprintInfoFromHashAsync(graphqlClient, projectId, hash) {
308
+ const fingerprintFragment = await getFingerprintFragmentFromHashAsync(graphqlClient, projectId, hash);
309
+ const fingerprint = await getFingerprintFromFingerprintFragmentAsync(fingerprintFragment);
310
+ let platforms;
311
+ const fingerprintBuilds = fingerprintFragment.builds?.edges.map(edge => edge.node) ?? [];
312
+ const fingerprintUpdates = fingerprintFragment.updates?.edges.map(edge => edge.node) ?? [];
313
+ if (fingerprintBuilds.length > 0) {
314
+ platforms = [fingerprintBuilds[0].platform];
315
+ }
316
+ else if (fingerprintUpdates.length > 0) {
317
+ platforms = [stringToAppPlatform(fingerprintUpdates[0].platform)];
318
+ }
319
+ return {
320
+ fingerprint,
321
+ platforms,
322
+ origin: {
323
+ type: FingerprintOriginType.Hash,
324
+ },
325
+ };
326
+ }
327
+ async function getFingerprintInfoFromUpdateGroupIdOrUpdateIdAsync(graphqlClient, projectId, nonInteractive, updateGroupIdOrUpdateId) {
328
+ // Some people may pass in update group id instead of update id, so add interactive support for that
329
+ try {
330
+ const maybeUpdateGroupId = updateGroupIdOrUpdateId;
331
+ const updateGroup = await UpdateQuery_1.UpdateQuery.viewUpdateGroupAsync(graphqlClient, {
332
+ groupId: maybeUpdateGroupId,
333
+ });
334
+ if (updateGroup.length === 1) {
335
+ const update = updateGroup[0];
336
+ return await getFingerprintFromUpdateFragmentAsync(update);
337
+ }
338
+ if (nonInteractive) {
339
+ const [accountName, project] = await Promise.all([
340
+ (await (0, projectUtils_1.getOwnerAccountForProjectIdAsync)(graphqlClient, projectId)).name,
341
+ AppQuery_1.AppQuery.byIdAsync(graphqlClient, projectId),
342
+ ]);
343
+ const updateUrl = (0, api_1.getExpoWebsiteBaseUrl)() +
344
+ `/accounts/${accountName}/projects/${project.name}/updates/${maybeUpdateGroupId}`;
345
+ throw new Error(`Please pass in your update ID from ${updateUrl} or use interactive mode to select the update ID.`);
346
+ }
347
+ const update = await (0, prompts_1.selectAsync)('Select a platform to compute the fingerprint from', updateGroup.map(update => ({
348
+ title: update.platform,
349
+ value: update,
350
+ })));
351
+ return await getFingerprintFromUpdateFragmentAsync(update);
352
+ }
353
+ catch (error) {
354
+ if (!error?.message.includes('Could not find any updates with group ID')) {
355
+ throw error;
356
+ }
357
+ }
358
+ const updateId = updateGroupIdOrUpdateId;
359
+ const updateWithFingerprint = await UpdateQuery_1.UpdateQuery.viewByUpdateAsync(graphqlClient, {
360
+ updateId,
361
+ });
362
+ return await getFingerprintFromUpdateFragmentAsync(updateWithFingerprint);
363
+ }
364
+ async function getFingerprintInfoFromBuildIdAsync(graphqlClient, buildId) {
365
+ const buildWithFingerprint = await BuildQuery_1.BuildQuery.withFingerprintByIdAsync(graphqlClient, buildId);
366
+ if (!buildWithFingerprint.fingerprint) {
367
+ throw new Error(`Fingerprint for build ${buildId} was not computed.`);
368
+ }
369
+ else if (!buildWithFingerprint.fingerprint.debugInfoUrl) {
370
+ throw new Error(`Fingerprint source for build ${buildId} was not computed.`);
371
+ }
372
+ return {
373
+ fingerprint: await getFingerprintFromFingerprintFragmentAsync(buildWithFingerprint.fingerprint),
374
+ platforms: [buildWithFingerprint.platform],
375
+ origin: {
376
+ type: FingerprintOriginType.Build,
377
+ build: buildWithFingerprint,
378
+ },
379
+ };
380
+ }
244
381
  async function getFingerprintFragmentFromHashAsync(graphqlClient, projectId, hash) {
245
382
  const fingerprint = await FingerprintQuery_1.FingerprintQuery.byHashAsync(graphqlClient, {
246
383
  appId: projectId,
@@ -412,6 +549,21 @@ function printContentsDiff(contents1, contents2) {
412
549
  : stringifiedContents2;
413
550
  (0, fingerprintDiff_1.abridgedDiff)(prettifiedContents1, prettifiedContents2, 0);
414
551
  }
552
+ function prettyPrintFingerprint(fingerprint, origin) {
553
+ if (origin.type === FingerprintOriginType.Project) {
554
+ return `fingerprint ${fingerprint.hash} from local directory`;
555
+ }
556
+ else if (origin.type === FingerprintOriginType.Update) {
557
+ return `fingerprint ${fingerprint.hash} from ${origin.update?.platform ? stringToAppPlatform(origin.update?.platform) : ''} ${origin.type}`;
558
+ }
559
+ else if (origin.type === FingerprintOriginType.Build) {
560
+ return `fingerprint ${fingerprint.hash} from ${origin.build?.platform} ${origin.type}`;
561
+ }
562
+ return `fingerprint ${fingerprint.hash}`;
563
+ }
564
+ function capitalizeFirstLetter(string) {
565
+ return string.charAt(0).toUpperCase() + string.slice(1);
566
+ }
415
567
  function isJSON(str) {
416
568
  try {
417
569
  JSON.parse(str);
@@ -108,7 +108,10 @@ class Onboarding extends EasCommand_1.default {
108
108
  targetProjectDir: initialTargetProjectDirectory,
109
109
  cloneMethod,
110
110
  });
111
- const vcsClient = new git_2.default(finalTargetProjectDirectory);
111
+ const vcsClient = new git_2.default({
112
+ maybeCwdOverride: finalTargetProjectDirectory,
113
+ requireCommit: false,
114
+ });
112
115
  if (!app.githubRepository) {
113
116
  await fs_extra_1.default.remove(path_1.default.join(finalTargetProjectDirectory, '.git'));
114
117
  await (0, runCommand_1.runCommandAsync)({
@@ -13,8 +13,7 @@ const AndroidSubmitCommand_1 = tslib_1.__importDefault(require("../../submit/and
13
13
  const context_1 = require("../../submit/context");
14
14
  const IosSubmitCommand_1 = tslib_1.__importDefault(require("../../submit/ios/IosSubmitCommand"));
15
15
  const json_1 = require("../../utils/json");
16
- const gitNoCommit_1 = tslib_1.__importDefault(require("../../vcs/clients/gitNoCommit"));
17
- const noVcs_1 = tslib_1.__importDefault(require("../../vcs/clients/noVcs"));
16
+ const git_1 = tslib_1.__importDefault(require("../../vcs/clients/git"));
18
17
  /**
19
18
  * This command will be run on the EAS workers.
20
19
  * This command resolves credentials and other
@@ -49,9 +48,14 @@ class SubmitInternal extends EasCommand_1.default {
49
48
  (0, json_1.enableJsonOutput)();
50
49
  const { loggedIn: { actor, graphqlClient }, privateProjectConfig: { exp, projectId, projectDir }, analytics, vcsClient, } = await this.getContextAsync(SubmitInternal, {
51
50
  nonInteractive: true,
52
- vcsClientOverride: process.env.EAS_NO_VCS ? new noVcs_1.default() : new gitNoCommit_1.default(),
53
51
  withServerSideEnvironment: null,
54
52
  });
53
+ if (vcsClient instanceof git_1.default) {
54
+ // `build:internal` is run on EAS workers and the repo may have been changed
55
+ // by pre-install hooks or other scripts. We don't want to require committing changes
56
+ // to continue the build.
57
+ vcsClient.requireCommit = false;
58
+ }
55
59
  const submissionProfile = await eas_json_1.EasJsonUtils.getSubmitProfileAsync(eas_json_1.EasJsonAccessor.fromProjectPath(projectDir), flags.platform, flags.profile);
56
60
  const ctx = await (0, context_1.createSubmissionContextAsync)({
57
61
  platform: flags.platform,