From e9fa4e820fcf06b91173fa67f967341937775a3f Mon Sep 17 00:00:00 2001 From: Radoslaw Krzemien Date: Fri, 12 Jan 2024 15:26:46 +0100 Subject: [PATCH 01/17] [eas-cli] Add command to delete build Added a new command build:delete. Shares some code with build:cancel, so I have moved the redundancies to a utils file at src/commandUtils/builds See: https://linear.app/expo/issue/ENG-11026/add-eas-builddelete-command --- packages/eas-cli/graphql.schema.json | 134 +++++++++++++++--- packages/eas-cli/src/commandUtils/builds.ts | 68 +++++++++ packages/eas-cli/src/commands/build/cancel.ts | 58 +------- packages/eas-cli/src/commands/build/delete.ts | 122 ++++++++++++++++ packages/eas-cli/src/graphql/generated.ts | 25 +++- 5 files changed, 337 insertions(+), 70 deletions(-) create mode 100644 packages/eas-cli/src/commandUtils/builds.ts create mode 100644 packages/eas-cli/src/commands/build/delete.ts diff --git a/packages/eas-cli/graphql.schema.json b/packages/eas-cli/graphql.schema.json index 6b0ce2d649..3e04fcb88f 100644 --- a/packages/eas-cli/graphql.schema.json +++ b/packages/eas-cli/graphql.schema.json @@ -1213,6 +1213,22 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "isDisabled", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "isSSOEnabled", "description": "Whether this account has SSO enabled. Can be queried by all members.", @@ -1717,6 +1733,18 @@ "description": null, "fields": null, "inputFields": [ + { + "name": "searchTerm", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "sortByField", "description": null, @@ -3117,6 +3145,33 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "AccountUsageEASJobsMetadata", + "description": null, + "fields": [ + { + "name": "resourceClassDisplayName", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "UNION", "name": "AccountUsageMetadata", @@ -3130,6 +3185,11 @@ "kind": "OBJECT", "name": "AccountUsageEASBuildMetadata", "ofType": null + }, + { + "kind": "OBJECT", + "name": "AccountUsageEASJobsMetadata", + "ofType": null } ] }, @@ -14944,6 +15004,18 @@ "name": "BuildAnnotation", "description": null, "fields": [ + { + "name": "authorUsername", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "buildPhase", "description": null, @@ -15016,6 +15088,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "regexFlags", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "regexString", "description": null, @@ -15116,6 +15200,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "regexFlags", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "regexString", "description": null, @@ -16546,18 +16642,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "buildMode", - "description": null, - "type": { - "kind": "ENUM", - "name": "BuildMode", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "buildProfile", "description": null, @@ -21091,6 +21175,12 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "JOBS", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "UPDATES", "description": null, @@ -21132,6 +21222,12 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "RUN_TIME", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "UNIQUE_UPDATERS", "description": null, @@ -37385,14 +37481,14 @@ { "name": "EAS_BUILD_PROJECT_SOURCES", "description": null, - "isDeprecated": false, - "deprecationReason": null + "isDeprecated": true, + "deprecationReason": "Use EAS_BUILD_GCS_PROJECT_SOURCES instead." }, { "name": "EAS_SUBMIT_APP_ARCHIVE", "description": null, - "isDeprecated": false, - "deprecationReason": null + "isDeprecated": true, + "deprecationReason": "Use EAS_SUBMIT_GCS_APP_ARCHIVE instead." }, { "name": "EAS_SUBMIT_GCS_APP_ARCHIVE", @@ -37530,6 +37626,12 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "MINUTE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "REQUEST", "description": null, diff --git a/packages/eas-cli/src/commandUtils/builds.ts b/packages/eas-cli/src/commandUtils/builds.ts new file mode 100644 index 0000000000..29d6121f06 --- /dev/null +++ b/packages/eas-cli/src/commandUtils/builds.ts @@ -0,0 +1,68 @@ +import chalk from 'chalk'; + +import { Build, BuildFragment, BuildStatus } from '../graphql/generated'; +import { BuildQuery } from '../graphql/queries/BuildQuery'; +import { appPlatformEmojis } from '../platform'; +import { ExpoGraphqlClient } from './context/contextUtils/createGraphqlClient'; + +export async function ensureBuildExistsAsync( + graphqlClient: ExpoGraphqlClient, + buildId: string +): Promise { + try { + await BuildQuery.byIdAsync(graphqlClient, buildId); + } catch { + throw new Error(`Couldn't find a build matching the id ${buildId}`); + } +} + +export async function fetchBuildsAsync( + graphqlClient: ExpoGraphqlClient, + projectId: string, + statuses?: BuildStatus[] +): Promise { + let builds = []; + if (!statuses) { + builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, { + appId: projectId, + offset: 0, + limit: 10, + }); + } else { + for (const status of statuses) { + const buildsForStatus = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, { + appId: projectId, + offset: 0, + limit: 10, + filter: { status }, + }); + builds.push(...buildsForStatus); + } + } + return builds; +} + +export function formatBuild( + build: Pick +): string { + const platform = appPlatformEmojis[build.platform]; + const startTime = new Date(build.createdAt).toLocaleString(); + let statusText: string; + if (build.status === BuildStatus.New) { + statusText = 'new'; + } else if (build.status === BuildStatus.InQueue) { + statusText = 'in queue'; + } else if (build.status === BuildStatus.InProgress) { + statusText = 'in progress'; + } else if (build.status === BuildStatus.Finished) { + statusText = 'finished'; + } else if (build.status === BuildStatus.Errored) { + statusText = 'errored'; + } else if ([BuildStatus.PendingCancel, BuildStatus.Canceled].includes(build.status)) { + statusText = 'canceled'; + } else { + statusText = 'unknown'; + } + const status = chalk.blue(statusText); + return `${platform} Started at: ${startTime}, Status: ${status}, Id: ${build.id}`; +} diff --git a/packages/eas-cli/src/commands/build/cancel.ts b/packages/eas-cli/src/commands/build/cancel.ts index 28db2d99f9..ba88150bd9 100644 --- a/packages/eas-cli/src/commands/build/cancel.ts +++ b/packages/eas-cli/src/commands/build/cancel.ts @@ -1,7 +1,7 @@ -import chalk from 'chalk'; import gql from 'graphql-tag'; import EasCommand from '../../commandUtils/EasCommand'; +import { ensureBuildExistsAsync, fetchBuildsAsync, formatBuild } from '../../commandUtils/builds'; import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient'; import { EASNonInteractiveFlag } from '../../commandUtils/flags'; import { withErrorHandlingAsync } from '../../graphql/client'; @@ -11,10 +11,8 @@ import { CancelBuildMutation, CancelBuildMutationVariables, } from '../../graphql/generated'; -import { BuildQuery } from '../../graphql/queries/BuildQuery'; import Log from '../../log'; import { ora } from '../../ora'; -import { appPlatformEmojis } from '../../platform'; import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils'; import { confirmAsync, selectAsync } from '../../prompts'; @@ -42,23 +40,6 @@ async function cancelBuildAsync( return data.build!.cancel; } -function formatUnfinishedBuild( - build: Pick -): string { - const platform = appPlatformEmojis[build.platform]; - const startTime = new Date(build.createdAt).toLocaleString(); - let statusText: string; - if (build.status === BuildStatus.New) { - statusText = 'new'; - } else if (build.status === BuildStatus.InQueue) { - statusText = 'in queue'; - } else { - statusText = 'in progress'; - } - const status = chalk.blue(statusText); - return `${platform} Started at: ${startTime}, Status: ${status}, Id: ${build.id}`; -} - export async function selectBuildToCancelAsync( graphqlClient: ExpoGraphqlClient, projectId: string, @@ -68,28 +49,12 @@ export async function selectBuildToCancelAsync( let builds; try { - const [newBuilds, inQueueBuilds, inProgressBuilds] = await Promise.all([ - BuildQuery.viewBuildsOnAppAsync(graphqlClient, { - appId: projectId, - offset: 0, - limit: 10, - filter: { status: BuildStatus.New }, - }), - BuildQuery.viewBuildsOnAppAsync(graphqlClient, { - appId: projectId, - offset: 0, - limit: 10, - filter: { status: BuildStatus.InQueue }, - }), - BuildQuery.viewBuildsOnAppAsync(graphqlClient, { - appId: projectId, - offset: 0, - limit: 10, - filter: { status: BuildStatus.InProgress }, - }), + builds = await fetchBuildsAsync(graphqlClient, projectId, [ + BuildStatus.New, + BuildStatus.InQueue, + BuildStatus.InProgress, ]); spinner.stop(); - builds = [...newBuilds, ...inQueueBuilds, ...inProgressBuilds]; } catch (error) { spinner.fail( `Something went wrong and we couldn't fetch the builds for the project ${projectDisplayName}.` @@ -103,7 +68,7 @@ export async function selectBuildToCancelAsync( const buildId = await selectAsync( 'Which build do you want to cancel?', builds.map(build => ({ - title: formatUnfinishedBuild(build), + title: formatBuild(build), value: build.id, })) ); @@ -116,17 +81,6 @@ export async function selectBuildToCancelAsync( } } -async function ensureBuildExistsAsync( - graphqlClient: ExpoGraphqlClient, - buildId: string -): Promise { - try { - await BuildQuery.byIdAsync(graphqlClient, buildId); - } catch { - throw new Error(`Couldn't find a build matching the id ${buildId}`); - } -} - export default class BuildCancel extends EasCommand { static override description = 'cancel a build'; diff --git a/packages/eas-cli/src/commands/build/delete.ts b/packages/eas-cli/src/commands/build/delete.ts new file mode 100644 index 0000000000..152d6ac3cc --- /dev/null +++ b/packages/eas-cli/src/commands/build/delete.ts @@ -0,0 +1,122 @@ +import gql from 'graphql-tag'; + +import EasCommand from '../../commandUtils/EasCommand'; +import { ensureBuildExistsAsync, fetchBuildsAsync, formatBuild } from '../../commandUtils/builds'; +import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient'; +import { EASNonInteractiveFlag } from '../../commandUtils/flags'; +import { withErrorHandlingAsync } from '../../graphql/client'; +import { Build, DeleteBuildMutation, DeleteBuildMutationVariables } from '../../graphql/generated'; +import Log from '../../log'; +import { ora } from '../../ora'; +import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils'; +import { confirmAsync, selectAsync } from '../../prompts'; + +async function deleteBuildAsync( + graphqlClient: ExpoGraphqlClient, + buildId: string +): Promise> { + const data = await withErrorHandlingAsync( + graphqlClient + .mutation( + gql` + mutation DeleteBuildMutation($buildId: ID!) { + build(buildId: $buildId) { + deleteBuild(buildId: $buildId) { + id + } + } + } + `, + { buildId } + ) + .toPromise() + ); + return data.build!.deleteBuild; +} + +export async function selectBuildToDeleteAsync( + graphqlClient: ExpoGraphqlClient, + projectId: string, + projectDisplayName: string +): Promise { + const spinner = ora().start('Fetching builds…'); + + let builds; + try { + builds = await fetchBuildsAsync(graphqlClient, projectId); + spinner.stop(); + } catch (error) { + spinner.fail( + `Something went wrong and we couldn't fetch the builds for the project ${projectDisplayName}.` + ); + throw error; + } + if (builds.length === 0) { + Log.warn(`There aren't any builds for the project ${projectDisplayName}.`); + return null; + } else { + const buildId = await selectAsync( + 'Which build do you want to delete?', + builds.map(build => ({ + title: formatBuild(build), + value: build.id, + })) + ); + + return (await confirmAsync({ + message: 'Are you sure you want to delete it?', + })) + ? buildId + : null; + } +} + +export default class BuildDelete extends EasCommand { + static override description = 'delete a build'; + static override args = [{ name: 'BUILD_ID' }]; + static override flags = { + ...EASNonInteractiveFlag, + }; + static override contextDefinition = { + ...this.ContextOptions.ProjectConfig, + ...this.ContextOptions.LoggedIn, + ...this.ContextOptions.Vcs, + }; + + async runAsync(): Promise { + const { + args: { BUILD_ID: buildIdFromArg }, + flags: { 'non-interactive': nonInteractive }, + } = await this.parse(BuildDelete); + const { + privateProjectConfig: { projectId }, + loggedIn: { graphqlClient }, + } = await this.getContextAsync(BuildDelete, { nonInteractive }); + const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId); + + if (buildIdFromArg) { + await ensureBuildExistsAsync(graphqlClient, buildIdFromArg); + } + + let buildId: string | null = buildIdFromArg; + if (!buildId) { + if (nonInteractive) { + throw new Error('BUILD_ID must not be empty in non-interactive mode'); + } + + buildId = await selectBuildToDeleteAsync(graphqlClient, projectId, displayName); + if (!buildId) { + return; + } + } + + const spinner = ora().start('Deleting the build...'); + try { + await deleteBuildAsync(graphqlClient, buildId); + spinner.succeed('Build deleted'); + } catch (error) { + spinner.fail(`Something went wrong and we couldn't delete your build ${buildId}`); + throw error; + } + } +} diff --git a/packages/eas-cli/src/graphql/generated.ts b/packages/eas-cli/src/graphql/generated.ts index f3cb437c16..32da7de9ad 100644 --- a/packages/eas-cli/src/graphql/generated.ts +++ b/packages/eas-cli/src/graphql/generated.ts @@ -112,6 +112,7 @@ export type Account = { googleServiceAccountKeys: Array; id: Scalars['ID']; isCurrent: Scalars['Boolean']; + isDisabled: Scalars['Boolean']; /** Whether this account has SSO enabled. Can be queried by all members. */ isSSOEnabled: Scalars['Boolean']; name: Scalars['String']; @@ -314,6 +315,7 @@ export type AccountAppsEdge = { }; export type AccountAppsFilterInput = { + searchTerm?: InputMaybe; sortByField: AccountAppsSortByField; }; @@ -540,7 +542,12 @@ export type AccountUsageEasBuildMetadata = { platform: AppPlatform; }; -export type AccountUsageMetadata = AccountUsageEasBuildMetadata; +export type AccountUsageEasJobsMetadata = { + __typename?: 'AccountUsageEASJobsMetadata'; + resourceClassDisplayName: Scalars['String']; +}; + +export type AccountUsageMetadata = AccountUsageEasBuildMetadata | AccountUsageEasJobsMetadata; export type AccountUsageMetric = { __typename?: 'AccountUsageMetric'; @@ -2159,11 +2166,13 @@ export type BuildRetryDisabledReasonArgs = { export type BuildAnnotation = { __typename?: 'BuildAnnotation'; + authorUsername?: Maybe; buildPhase: Scalars['String']; exampleBuildLog?: Maybe; id: Scalars['ID']; internalNotes?: Maybe; message: Scalars['String']; + regexFlags?: Maybe; regexString: Scalars['String']; title: Scalars['String']; }; @@ -2173,6 +2182,7 @@ export type BuildAnnotationDataInput = { exampleBuildLog?: InputMaybe; internalNotes?: InputMaybe; message: Scalars['String']; + regexFlags?: InputMaybe; regexString: Scalars['String']; title: Scalars['String']; }; @@ -2375,7 +2385,6 @@ export type BuildMetadataInput = { appIdentifier?: InputMaybe; appName?: InputMaybe; appVersion?: InputMaybe; - buildMode?: InputMaybe; buildProfile?: InputMaybe; channel?: InputMaybe; cliVersion?: InputMaybe; @@ -3073,6 +3082,7 @@ export type EasBuildOrClassicBuildJob = Build | BuildJob; export enum EasService { Builds = 'BUILDS', + Jobs = 'JOBS', Updates = 'UPDATES' } @@ -3081,6 +3091,7 @@ export enum EasServiceMetric { BandwidthUsage = 'BANDWIDTH_USAGE', Builds = 'BUILDS', ManifestRequests = 'MANIFEST_REQUESTS', + RunTime = 'RUN_TIME', UniqueUpdaters = 'UNIQUE_UPDATERS', UniqueUsers = 'UNIQUE_USERS' } @@ -5390,7 +5401,9 @@ export type UploadSessionCreateUploadSessionArgs = { export enum UploadSessionType { EasBuildGcsProjectSources = 'EAS_BUILD_GCS_PROJECT_SOURCES', + /** @deprecated Use EAS_BUILD_GCS_PROJECT_SOURCES instead. */ EasBuildProjectSources = 'EAS_BUILD_PROJECT_SOURCES', + /** @deprecated Use EAS_SUBMIT_GCS_APP_ARCHIVE instead. */ EasSubmitAppArchive = 'EAS_SUBMIT_APP_ARCHIVE', EasSubmitGcsAppArchive = 'EAS_SUBMIT_GCS_APP_ARCHIVE' } @@ -5408,6 +5421,7 @@ export type UsageMetricTotal = { export enum UsageMetricType { Bandwidth = 'BANDWIDTH', Build = 'BUILD', + Minute = 'MINUTE', Request = 'REQUEST', Update = 'UPDATE', User = 'USER' @@ -6027,6 +6041,13 @@ export type CancelBuildMutationVariables = Exact<{ export type CancelBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', cancel: { __typename?: 'Build', id: string, status: BuildStatus } } }; +export type DeleteBuildMutationVariables = Exact<{ + buildId: Scalars['ID']; +}>; + + +export type DeleteBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', deleteBuild: { __typename?: 'Build', id: string } } }; + export type DeleteUpdateChannelMutationVariables = Exact<{ channelId: Scalars['ID']; }>; From 3163eb69694b464a3c4f4c57cb45aaa5cd2cfbe6 Mon Sep 17 00:00:00 2001 From: Radoslaw Krzemien Date: Fri, 12 Jan 2024 15:28:00 +0100 Subject: [PATCH 02/17] [eas-cli] Add tests Added a test file based on existing tests for build:cancel command. Adding more tests See: https://linear.app/expo/issue/ENG-11026/add-eas-builddelete-command --- .../src/build/__tests__/delete-test.ts | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 packages/eas-cli/src/build/__tests__/delete-test.ts diff --git a/packages/eas-cli/src/build/__tests__/delete-test.ts b/packages/eas-cli/src/build/__tests__/delete-test.ts new file mode 100644 index 0000000000..e72fa699e2 --- /dev/null +++ b/packages/eas-cli/src/build/__tests__/delete-test.ts @@ -0,0 +1,96 @@ +import { instance, mock } from 'ts-mockito'; +import { v4 as uuid } from 'uuid'; + +import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient'; +import { selectBuildToDeleteAsync } from '../../commands/build/delete'; +import { + AppPlatform, + BuildFragment, + BuildPriority, + BuildResourceClass, + BuildStatus, +} from '../../graphql/generated'; +import { BuildQuery } from '../../graphql/queries/BuildQuery'; +import { confirmAsync, selectAsync } from '../../prompts'; + +jest.mock('../../ora', () => ({ + ora: jest.fn().mockImplementation(() => ({ + start: jest.fn().mockImplementation(() => ({ + stop: jest.fn(), + fail: jest.fn(), + })), + })), +})); +jest.mock('../../prompts', () => { + return { + selectAsync: jest.fn(), + confirmAsync: jest.fn(), + }; +}); + +jest.mock('../../graphql/queries/BuildQuery', () => { + const actual = jest.requireActual('../../graphql/queries/BuildQuery'); + return { + BuildQuery: { + ...actual.BuildQuery, + viewBuildsOnAppAsync: jest.fn(), + }, + }; +}); + +describe(selectBuildToDeleteAsync.name, () => { + const selectedBuildId = uuid(); + const projectId = uuid(); + + beforeEach(() => { + jest + .mocked(BuildQuery.viewBuildsOnAppAsync) + .mockImplementation(async () => [ + createMockBuildFragment({ projectId, buildId: selectedBuildId }), + ]); + }); + + it('does not return build id when confirmation is rejected', async () => { + const graphqlClient = instance(mock()); + jest.mocked(confirmAsync).mockResolvedValueOnce(false); + expect(selectBuildToDeleteAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(null); + }); + + it('returns build id when confirmation is confirmed', async () => { + const graphqlClient = instance(mock()); + jest.mocked(selectAsync).mockResolvedValueOnce(selectedBuildId); + jest.mocked(confirmAsync).mockResolvedValueOnce(true); + expect(selectBuildToDeleteAsync(graphqlClient, projectId, 'blah')).resolves.toEqual( + selectedBuildId + ); + }); +}); + +function createMockBuildFragment({ + projectId, + buildId, +}: { + projectId: string; + buildId?: string; +}): BuildFragment { + return { + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + platform: AppPlatform.Android, + id: buildId ?? uuid(), + priority: BuildPriority.Normal, + project: { + __typename: 'App', + slug: 'test-project', + id: projectId, + name: 'test-project', + ownerAccount: { + __typename: 'Account', + id: uuid(), + name: 'test-account', + }, + }, + status: BuildStatus.InQueue, + resourceClass: BuildResourceClass.AndroidMedium, + }; +} From 66bb00f7587910709849f207da9d0fdcb2b22dbb Mon Sep 17 00:00:00 2001 From: Radoslaw Krzemien Date: Mon, 15 Jan 2024 13:13:48 +0100 Subject: [PATCH 03/17] [eas-cli] Sort builds Explicitly sorts fetched builds by descending order of `created_at`. In case multiple results were combined (for different statuses) they would by already sorted, but grouped by status also See: https://linear.app/expo/issue/ENG-11026/add-eas-builddelete-command --- packages/eas-cli/src/commandUtils/builds.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/eas-cli/src/commandUtils/builds.ts b/packages/eas-cli/src/commandUtils/builds.ts index 29d6121f06..2315c74281 100644 --- a/packages/eas-cli/src/commandUtils/builds.ts +++ b/packages/eas-cli/src/commandUtils/builds.ts @@ -39,6 +39,7 @@ export async function fetchBuildsAsync( builds.push(...buildsForStatus); } } + builds.sort((buildA, buildB) => (buildA.createdAt > buildB.createdAt ? -1 : 1)); return builds; } From d1c3adaa433cbf86e8bc69c1762ba43ce2e4acca Mon Sep 17 00:00:00 2001 From: Radoslaw Krzemien Date: Mon, 15 Jan 2024 13:17:28 +0100 Subject: [PATCH 04/17] [eas-cli] Use object arg Changed `fetchBuildsAsync` arguments into single object argument for readability See: https://linear.app/expo/issue/ENG-11026/add-eas-builddelete-command --- packages/eas-cli/src/commandUtils/builds.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/eas-cli/src/commandUtils/builds.ts b/packages/eas-cli/src/commandUtils/builds.ts index 2315c74281..5eaa0b5874 100644 --- a/packages/eas-cli/src/commandUtils/builds.ts +++ b/packages/eas-cli/src/commandUtils/builds.ts @@ -16,11 +16,15 @@ export async function ensureBuildExistsAsync( } } -export async function fetchBuildsAsync( - graphqlClient: ExpoGraphqlClient, - projectId: string, - statuses?: BuildStatus[] -): Promise { +export async function fetchBuildsAsync({ + graphqlClient, + projectId, + statuses, +}: { + graphqlClient: ExpoGraphqlClient; + projectId: string; + statuses?: BuildStatus[]; +}): Promise { let builds = []; if (!statuses) { builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, { From 967a15a294790bd6b8489f65440ae586404cdad1 Mon Sep 17 00:00:00 2001 From: Radoslaw Krzemien Date: Mon, 15 Jan 2024 13:20:00 +0100 Subject: [PATCH 05/17] [eas-cli] Update msg Changed the message displayed when no builds are found for deletion/cancellation to be more human-like See: https://linear.app/expo/issue/ENG-11026/add-eas-builddelete-command --- packages/eas-cli/src/commands/build/cancel.ts | 12 ++++++------ packages/eas-cli/src/commands/build/delete.ts | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/eas-cli/src/commands/build/cancel.ts b/packages/eas-cli/src/commands/build/cancel.ts index ba88150bd9..b072ce6e43 100644 --- a/packages/eas-cli/src/commands/build/cancel.ts +++ b/packages/eas-cli/src/commands/build/cancel.ts @@ -49,11 +49,11 @@ export async function selectBuildToCancelAsync( let builds; try { - builds = await fetchBuildsAsync(graphqlClient, projectId, [ - BuildStatus.New, - BuildStatus.InQueue, - BuildStatus.InProgress, - ]); + builds = await fetchBuildsAsync({ + graphqlClient, + projectId, + statuses: [BuildStatus.New, BuildStatus.InQueue, BuildStatus.InProgress], + }); spinner.stop(); } catch (error) { spinner.fail( @@ -62,7 +62,7 @@ export async function selectBuildToCancelAsync( throw error; } if (builds.length === 0) { - Log.warn(`There aren't any uncompleted builds for the project ${projectDisplayName}.`); + Log.warn(`We couldn't find any uncompleted builds for the project ${projectDisplayName}.`); return null; } else { const buildId = await selectAsync( diff --git a/packages/eas-cli/src/commands/build/delete.ts b/packages/eas-cli/src/commands/build/delete.ts index 152d6ac3cc..cf8d6247c3 100644 --- a/packages/eas-cli/src/commands/build/delete.ts +++ b/packages/eas-cli/src/commands/build/delete.ts @@ -43,7 +43,7 @@ export async function selectBuildToDeleteAsync( let builds; try { - builds = await fetchBuildsAsync(graphqlClient, projectId); + builds = await fetchBuildsAsync({ graphqlClient, projectId }); spinner.stop(); } catch (error) { spinner.fail( @@ -52,7 +52,7 @@ export async function selectBuildToDeleteAsync( throw error; } if (builds.length === 0) { - Log.warn(`There aren't any builds for the project ${projectDisplayName}.`); + Log.warn(`We couldn't find any builds for the project ${projectDisplayName}.`); return null; } else { const buildId = await selectAsync( From fd40ac2bda9a662cd6b0d4eb126dfa370229d58a Mon Sep 17 00:00:00 2001 From: Radoslaw Krzemien Date: Mon, 15 Jan 2024 13:22:04 +0100 Subject: [PATCH 06/17] [eas-cli] Update msg Changed the message displayed when no build ID is provided for non-interactive mode, to not include the argument name but rather its description See: https://linear.app/expo/issue/ENG-11026/add-eas-builddelete-command --- packages/eas-cli/src/commands/build/cancel.ts | 2 +- packages/eas-cli/src/commands/build/delete.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eas-cli/src/commands/build/cancel.ts b/packages/eas-cli/src/commands/build/cancel.ts index b072ce6e43..b20d0c8d73 100644 --- a/packages/eas-cli/src/commands/build/cancel.ts +++ b/packages/eas-cli/src/commands/build/cancel.ts @@ -117,7 +117,7 @@ export default class BuildCancel extends EasCommand { let buildId: string | null = buildIdFromArg; if (!buildId) { if (nonInteractive) { - throw new Error('BUILD_ID must not be empty in non-interactive mode'); + throw new Error('Build ID must be provided in non-interactive mode'); } buildId = await selectBuildToCancelAsync(graphqlClient, projectId, displayName); diff --git a/packages/eas-cli/src/commands/build/delete.ts b/packages/eas-cli/src/commands/build/delete.ts index cf8d6247c3..65fecfc267 100644 --- a/packages/eas-cli/src/commands/build/delete.ts +++ b/packages/eas-cli/src/commands/build/delete.ts @@ -101,7 +101,7 @@ export default class BuildDelete extends EasCommand { let buildId: string | null = buildIdFromArg; if (!buildId) { if (nonInteractive) { - throw new Error('BUILD_ID must not be empty in non-interactive mode'); + throw new Error('Build ID must be provided in non-interactive mode'); } buildId = await selectBuildToDeleteAsync(graphqlClient, projectId, displayName); From 13a274daca0b87edbd695809f96ddb23329883c2 Mon Sep 17 00:00:00 2001 From: Radoslaw Krzemien Date: Mon, 15 Jan 2024 13:32:33 +0100 Subject: [PATCH 07/17] [eas-cli] Parallelize Changed for loop with async calls into await promise.all() See: https://linear.app/expo/issue/ENG-11026/add-eas-builddelete-command --- packages/eas-cli/src/commandUtils/builds.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/eas-cli/src/commandUtils/builds.ts b/packages/eas-cli/src/commandUtils/builds.ts index 5eaa0b5874..4acd60855a 100644 --- a/packages/eas-cli/src/commandUtils/builds.ts +++ b/packages/eas-cli/src/commandUtils/builds.ts @@ -33,15 +33,18 @@ export async function fetchBuildsAsync({ limit: 10, }); } else { - for (const status of statuses) { - const buildsForStatus = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, { - appId: projectId, - offset: 0, - limit: 10, - filter: { status }, - }); - builds.push(...buildsForStatus); - } + builds = ( + await Promise.all( + statuses.map(status => + BuildQuery.viewBuildsOnAppAsync(graphqlClient, { + appId: projectId, + offset: 0, + limit: 10, + filter: { status }, + }) + ) + ) + ).flat(); } builds.sort((buildA, buildB) => (buildA.createdAt > buildB.createdAt ? -1 : 1)); return builds; From 0277dcf5d7938f817cfed9c6bdb41f8f074fc133 Mon Sep 17 00:00:00 2001 From: Radoslaw Krzemien Date: Tue, 16 Jan 2024 13:53:30 +0100 Subject: [PATCH 08/17] [eas-cli] Add filter flags When not providing an ID of the build to delete, the user can now filter the listed builds by platform and build profile using new flags. The same change has also been applied to `build:cancel` command See: https://linear.app/expo/issue/ENG-11026/add-eas-builddelete-command --- packages/eas-cli/src/commandUtils/builds.ts | 29 +++++++++++++----- packages/eas-cli/src/commands/build/cancel.ts | 30 ++++++++++++++++--- packages/eas-cli/src/commands/build/delete.ts | 27 ++++++++++++++--- 3 files changed, 71 insertions(+), 15 deletions(-) diff --git a/packages/eas-cli/src/commandUtils/builds.ts b/packages/eas-cli/src/commandUtils/builds.ts index 4acd60855a..7464a0583a 100644 --- a/packages/eas-cli/src/commandUtils/builds.ts +++ b/packages/eas-cli/src/commandUtils/builds.ts @@ -1,6 +1,13 @@ import chalk from 'chalk'; -import { Build, BuildFragment, BuildStatus } from '../graphql/generated'; +import { + AppPlatform, + Build, + BuildFilter, + BuildFragment, + BuildStatus, + InputMaybe, +} from '../graphql/generated'; import { BuildQuery } from '../graphql/queries/BuildQuery'; import { appPlatformEmojis } from '../platform'; import { ExpoGraphqlClient } from './context/contextUtils/createGraphqlClient'; @@ -19,28 +26,36 @@ export async function ensureBuildExistsAsync( export async function fetchBuildsAsync({ graphqlClient, projectId, - statuses, + filters, }: { graphqlClient: ExpoGraphqlClient; projectId: string; - statuses?: BuildStatus[]; + filters?: { statuses?: BuildStatus[]; platform?: string; profile?: string }; }): Promise { - let builds = []; - if (!statuses) { + let builds: BuildFragment[]; + const queryFilters: InputMaybe = {}; + if (filters?.platform && filters?.platform !== 'all') { + queryFilters['platform'] = filters?.platform.toUpperCase() as AppPlatform; + } + if (filters?.profile) { + queryFilters['buildProfile'] = filters?.profile; + } + if (!filters?.statuses) { builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, { appId: projectId, offset: 0, limit: 10, + filter: queryFilters ? queryFilters : undefined, }); } else { builds = ( await Promise.all( - statuses.map(status => + filters.statuses.map(status => BuildQuery.viewBuildsOnAppAsync(graphqlClient, { appId: projectId, offset: 0, limit: 10, - filter: { status }, + filter: queryFilters ? { ...queryFilters, status } : { status }, }) ) ) diff --git a/packages/eas-cli/src/commands/build/cancel.ts b/packages/eas-cli/src/commands/build/cancel.ts index b20d0c8d73..68968a2f39 100644 --- a/packages/eas-cli/src/commands/build/cancel.ts +++ b/packages/eas-cli/src/commands/build/cancel.ts @@ -1,3 +1,4 @@ +import { Flags } from '@oclif/core'; import gql from 'graphql-tag'; import EasCommand from '../../commandUtils/EasCommand'; @@ -43,7 +44,11 @@ async function cancelBuildAsync( export async function selectBuildToCancelAsync( graphqlClient: ExpoGraphqlClient, projectId: string, - projectDisplayName: string + projectDisplayName: string, + filters?: { + platform?: string; + profile?: string; + } ): Promise { const spinner = ora().start('Fetching the uncompleted builds…'); @@ -52,7 +57,10 @@ export async function selectBuildToCancelAsync( builds = await fetchBuildsAsync({ graphqlClient, projectId, - statuses: [BuildStatus.New, BuildStatus.InQueue, BuildStatus.InProgress], + filters: { + ...filters, + statuses: [BuildStatus.New, BuildStatus.InQueue, BuildStatus.InProgress], + }, }); spinner.stop(); } catch (error) { @@ -88,6 +96,17 @@ export default class BuildCancel extends EasCommand { static override flags = { ...EASNonInteractiveFlag, + platform: Flags.string({ + char: 'p', + description: 'Platform for which to list builds when ID not provided', + options: ['android', 'ios', 'all'], + }), + profile: Flags.string({ + char: 'e', + description: + 'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.', + helpValue: 'PROFILE_NAME', + }), }; static override contextDefinition = { @@ -99,7 +118,7 @@ export default class BuildCancel extends EasCommand { async runAsync(): Promise { const { args: { BUILD_ID: buildIdFromArg }, - flags: { 'non-interactive': nonInteractive }, + flags: { 'non-interactive': nonInteractive, platform, profile }, } = await this.parse(BuildCancel); const { privateProjectConfig: { projectId }, @@ -120,7 +139,10 @@ export default class BuildCancel extends EasCommand { throw new Error('Build ID must be provided in non-interactive mode'); } - buildId = await selectBuildToCancelAsync(graphqlClient, projectId, displayName); + buildId = await selectBuildToCancelAsync(graphqlClient, projectId, displayName, { + platform, + profile, + }); if (!buildId) { return; } diff --git a/packages/eas-cli/src/commands/build/delete.ts b/packages/eas-cli/src/commands/build/delete.ts index 65fecfc267..0cff91fd85 100644 --- a/packages/eas-cli/src/commands/build/delete.ts +++ b/packages/eas-cli/src/commands/build/delete.ts @@ -1,3 +1,4 @@ +import { Flags } from '@oclif/core'; import gql from 'graphql-tag'; import EasCommand from '../../commandUtils/EasCommand'; @@ -37,13 +38,17 @@ async function deleteBuildAsync( export async function selectBuildToDeleteAsync( graphqlClient: ExpoGraphqlClient, projectId: string, - projectDisplayName: string + projectDisplayName: string, + filters?: { + platform?: string; + profile?: string; + } ): Promise { const spinner = ora().start('Fetching builds…'); let builds; try { - builds = await fetchBuildsAsync({ graphqlClient, projectId }); + builds = await fetchBuildsAsync({ graphqlClient, projectId, filters }); spinner.stop(); } catch (error) { spinner.fail( @@ -76,6 +81,17 @@ export default class BuildDelete extends EasCommand { static override args = [{ name: 'BUILD_ID' }]; static override flags = { ...EASNonInteractiveFlag, + platform: Flags.string({ + char: 'p', + description: 'Platform for which to list builds when ID not provided', + options: ['android', 'ios', 'all'], + }), + profile: Flags.string({ + char: 'e', + description: + 'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.', + helpValue: 'PROFILE_NAME', + }), }; static override contextDefinition = { ...this.ContextOptions.ProjectConfig, @@ -86,7 +102,7 @@ export default class BuildDelete extends EasCommand { async runAsync(): Promise { const { args: { BUILD_ID: buildIdFromArg }, - flags: { 'non-interactive': nonInteractive }, + flags: { 'non-interactive': nonInteractive, platform, profile }, } = await this.parse(BuildDelete); const { privateProjectConfig: { projectId }, @@ -104,7 +120,10 @@ export default class BuildDelete extends EasCommand { throw new Error('Build ID must be provided in non-interactive mode'); } - buildId = await selectBuildToDeleteAsync(graphqlClient, projectId, displayName); + buildId = await selectBuildToDeleteAsync(graphqlClient, projectId, displayName, { + platform, + profile, + }); if (!buildId) { return; } From b8f97abb9d5f16b6853d54fe939a22d5e43eec07 Mon Sep 17 00:00:00 2001 From: Radoslaw Krzemien Date: Tue, 16 Jan 2024 13:07:23 +0000 Subject: [PATCH 09/17] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e23657b72..158cde9f96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This is the log of notable changes to EAS CLI and related packages. ### 🎉 New features - Allow undefined update message for EAS Update publishing when no VCS. ([#2148](https://github.com/expo/eas-cli/pull/2148) by [@wschurman](https://github.com/wschurman)) +- Added `build:delete` command. ([#2178](https://github.com/expo/eas-cli/pull/2178) by [@radoslawkrzemien](https://github.com/radoslawkrzemien)) ### 🐛 Bug fixes From 30647afe7f3b915211893534c13c9b335c4e730d Mon Sep 17 00:00:00 2001 From: Radoslaw Krzemien Date: Tue, 16 Jan 2024 13:08:20 +0000 Subject: [PATCH 10/17] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 158cde9f96..3a9547c442 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This is the log of notable changes to EAS CLI and related packages. - Allow undefined update message for EAS Update publishing when no VCS. ([#2148](https://github.com/expo/eas-cli/pull/2148) by [@wschurman](https://github.com/wschurman)) - Added `build:delete` command. ([#2178](https://github.com/expo/eas-cli/pull/2178) by [@radoslawkrzemien](https://github.com/radoslawkrzemien)) +- Add filter flags for `platform` and `profile` to `build:cancel` and `build:delete` commands. ([#2178](https://github.com/expo/eas-cli/pull/2178) by [@radoslawkrzemien](https://github.com/radoslawkrzemien)) ### 🐛 Bug fixes From 2a9820c5ab4ee1477a09df29b72d5b15aa60f3e0 Mon Sep 17 00:00:00 2001 From: Radoslaw Krzemien Date: Tue, 16 Jan 2024 14:09:03 +0100 Subject: [PATCH 11/17] [eas-cli] Rephrase Using imperative form in the changelog See: https://linear.app/expo/issue/ENG-11026/add-eas-builddelete-command --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a9547c442..58a66d3aea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ This is the log of notable changes to EAS CLI and related packages. ### 🎉 New features - Allow undefined update message for EAS Update publishing when no VCS. ([#2148](https://github.com/expo/eas-cli/pull/2148) by [@wschurman](https://github.com/wschurman)) -- Added `build:delete` command. ([#2178](https://github.com/expo/eas-cli/pull/2178) by [@radoslawkrzemien](https://github.com/radoslawkrzemien)) +- Add `build:delete` command. ([#2178](https://github.com/expo/eas-cli/pull/2178) by [@radoslawkrzemien](https://github.com/radoslawkrzemien)) - Add filter flags for `platform` and `profile` to `build:cancel` and `build:delete` commands. ([#2178](https://github.com/expo/eas-cli/pull/2178) by [@radoslawkrzemien](https://github.com/radoslawkrzemien)) ### 🐛 Bug fixes From b9fdaae7cc67a012d8b7a20b7fa2d1e7c4a785e1 Mon Sep 17 00:00:00 2001 From: Radoslaw Krzemien Date: Tue, 16 Jan 2024 15:12:12 +0100 Subject: [PATCH 12/17] [eas-cli] Validate args Disallow using both the build ID and filter flags when using `build:delete` and `build:cancel` commands See: https://linear.app/expo/issue/ENG-11026/add-eas-builddelete-command --- packages/eas-cli/src/commands/build/cancel.ts | 7 +++++++ packages/eas-cli/src/commands/build/delete.ts | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/packages/eas-cli/src/commands/build/cancel.ts b/packages/eas-cli/src/commands/build/cancel.ts index 68968a2f39..721454e3f3 100644 --- a/packages/eas-cli/src/commands/build/cancel.ts +++ b/packages/eas-cli/src/commands/build/cancel.ts @@ -120,6 +120,13 @@ export default class BuildCancel extends EasCommand { args: { BUILD_ID: buildIdFromArg }, flags: { 'non-interactive': nonInteractive, platform, profile }, } = await this.parse(BuildCancel); + + if (buildIdFromArg && (platform || profile)) { + throw new Error( + 'Build ID cannot be used together with platform and profile flags. They are used to filter the list of builds when not providing the build ID' + ); + } + const { privateProjectConfig: { projectId }, loggedIn: { graphqlClient }, diff --git a/packages/eas-cli/src/commands/build/delete.ts b/packages/eas-cli/src/commands/build/delete.ts index 0cff91fd85..056e838b20 100644 --- a/packages/eas-cli/src/commands/build/delete.ts +++ b/packages/eas-cli/src/commands/build/delete.ts @@ -104,6 +104,13 @@ export default class BuildDelete extends EasCommand { args: { BUILD_ID: buildIdFromArg }, flags: { 'non-interactive': nonInteractive, platform, profile }, } = await this.parse(BuildDelete); + + if (buildIdFromArg && (platform || profile)) { + throw new Error( + 'Build ID cannot be used together with platform and profile flags. They are used to filter the list of builds when not providing the build ID' + ); + } + const { privateProjectConfig: { projectId }, loggedIn: { graphqlClient }, From 96a475b0fed792361ed390d7cb4c591b86906ea5 Mon Sep 17 00:00:00 2001 From: Radoslaw Krzemien Date: Tue, 16 Jan 2024 15:13:56 +0100 Subject: [PATCH 13/17] [eas-cli] Update descriptions Updated the descriptions of the filter flags as per suggestion during review See: https://linear.app/expo/issue/ENG-11026/add-eas-builddelete-command --- packages/eas-cli/src/commands/build/cancel.ts | 5 ++--- packages/eas-cli/src/commands/build/delete.ts | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/eas-cli/src/commands/build/cancel.ts b/packages/eas-cli/src/commands/build/cancel.ts index 721454e3f3..424daa573a 100644 --- a/packages/eas-cli/src/commands/build/cancel.ts +++ b/packages/eas-cli/src/commands/build/cancel.ts @@ -98,13 +98,12 @@ export default class BuildCancel extends EasCommand { ...EASNonInteractiveFlag, platform: Flags.string({ char: 'p', - description: 'Platform for which to list builds when ID not provided', + description: 'Filter builds by the platform if build ID is not provided', options: ['android', 'ios', 'all'], }), profile: Flags.string({ char: 'e', - description: - 'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.', + description: 'Filter builds by build profile if build ID is not provided', helpValue: 'PROFILE_NAME', }), }; diff --git a/packages/eas-cli/src/commands/build/delete.ts b/packages/eas-cli/src/commands/build/delete.ts index 056e838b20..3e1cec9a4e 100644 --- a/packages/eas-cli/src/commands/build/delete.ts +++ b/packages/eas-cli/src/commands/build/delete.ts @@ -83,13 +83,12 @@ export default class BuildDelete extends EasCommand { ...EASNonInteractiveFlag, platform: Flags.string({ char: 'p', - description: 'Platform for which to list builds when ID not provided', + description: 'Filter builds by the platform if build ID is not provided', options: ['android', 'ios', 'all'], }), profile: Flags.string({ char: 'e', - description: - 'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.', + description: 'Filter builds by build profile if build ID is not provided', helpValue: 'PROFILE_NAME', }), }; From cb96ae371ed7f83f2a9f5c1a6c4f9d7a4ae505a7 Mon Sep 17 00:00:00 2001 From: Radoslaw Krzemien Date: Tue, 16 Jan 2024 15:24:19 +0100 Subject: [PATCH 14/17] [eas-cli] Add mapping Added function for mapping platform flag into generated graphQL value See: https://linear.app/expo/issue/ENG-11026/add-eas-builddelete-command --- packages/eas-cli/src/commandUtils/builds.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/eas-cli/src/commandUtils/builds.ts b/packages/eas-cli/src/commandUtils/builds.ts index 7464a0583a..4ba3e66968 100644 --- a/packages/eas-cli/src/commandUtils/builds.ts +++ b/packages/eas-cli/src/commandUtils/builds.ts @@ -34,11 +34,11 @@ export async function fetchBuildsAsync({ }): Promise { let builds: BuildFragment[]; const queryFilters: InputMaybe = {}; - if (filters?.platform && filters?.platform !== 'all') { - queryFilters['platform'] = filters?.platform.toUpperCase() as AppPlatform; + if (filters?.platform && filters.platform !== 'all') { + queryFilters['platform'] = toAppPlatform(filters.platform); } if (filters?.profile) { - queryFilters['buildProfile'] = filters?.profile; + queryFilters['buildProfile'] = filters.profile; } if (!filters?.statuses) { builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, { @@ -89,3 +89,9 @@ export function formatBuild( const status = chalk.blue(statusText); return `${platform} Started at: ${startTime}, Status: ${status}, Id: ${build.id}`; } + +function toAppPlatform(platform: string): AppPlatform { + const capitalizedPlatform = (platform[0].toUpperCase() + + platform.substring(1)) as keyof typeof AppPlatform; + return AppPlatform[capitalizedPlatform]; +} From 549b62c83e06cf5cbbfa5fa861caea15b693f4c3 Mon Sep 17 00:00:00 2001 From: Radoslaw Krzemien Date: Tue, 16 Jan 2024 15:30:52 +0100 Subject: [PATCH 15/17] [eas-cli] Update mapping Replaced function with a mapping object See: https://linear.app/expo/issue/ENG-11026/add-eas-builddelete-command --- packages/eas-cli/src/commandUtils/builds.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/eas-cli/src/commandUtils/builds.ts b/packages/eas-cli/src/commandUtils/builds.ts index 4ba3e66968..9992fa5cc0 100644 --- a/packages/eas-cli/src/commandUtils/builds.ts +++ b/packages/eas-cli/src/commandUtils/builds.ts @@ -12,6 +12,11 @@ import { BuildQuery } from '../graphql/queries/BuildQuery'; import { appPlatformEmojis } from '../platform'; import { ExpoGraphqlClient } from './context/contextUtils/createGraphqlClient'; +const platformToAppPlatform: Record = { + android: AppPlatform.Android, + ios: AppPlatform.Ios, +}; + export async function ensureBuildExistsAsync( graphqlClient: ExpoGraphqlClient, buildId: string @@ -35,7 +40,7 @@ export async function fetchBuildsAsync({ let builds: BuildFragment[]; const queryFilters: InputMaybe = {}; if (filters?.platform && filters.platform !== 'all') { - queryFilters['platform'] = toAppPlatform(filters.platform); + queryFilters['platform'] = platformToAppPlatform[filters.platform]; } if (filters?.profile) { queryFilters['buildProfile'] = filters.profile; @@ -89,9 +94,3 @@ export function formatBuild( const status = chalk.blue(statusText); return `${platform} Started at: ${startTime}, Status: ${status}, Id: ${build.id}`; } - -function toAppPlatform(platform: string): AppPlatform { - const capitalizedPlatform = (platform[0].toUpperCase() + - platform.substring(1)) as keyof typeof AppPlatform; - return AppPlatform[capitalizedPlatform]; -} From 334c95c115ec8e0ef6efdb509900364425e52608 Mon Sep 17 00:00:00 2001 From: Radoslaw Krzemien Date: Tue, 16 Jan 2024 16:05:18 +0100 Subject: [PATCH 16/17] [eas-cli] Use Flags.enum Replaced strings with object enums See: https://linear.app/expo/issue/ENG-11026/add-eas-builddelete-command --- packages/eas-cli/src/commandUtils/builds.ts | 9 ++++++--- packages/eas-cli/src/commands/build/cancel.ts | 7 ++++--- packages/eas-cli/src/commands/build/delete.ts | 7 ++++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/eas-cli/src/commandUtils/builds.ts b/packages/eas-cli/src/commandUtils/builds.ts index 9992fa5cc0..efe3266b35 100644 --- a/packages/eas-cli/src/commandUtils/builds.ts +++ b/packages/eas-cli/src/commandUtils/builds.ts @@ -9,10 +9,13 @@ import { InputMaybe, } from '../graphql/generated'; import { BuildQuery } from '../graphql/queries/BuildQuery'; -import { appPlatformEmojis } from '../platform'; +import { RequestedPlatform, appPlatformEmojis } from '../platform'; import { ExpoGraphqlClient } from './context/contextUtils/createGraphqlClient'; -const platformToAppPlatform: Record = { +const platformToAppPlatform: Record< + Exclude, + AppPlatform | undefined +> = { android: AppPlatform.Android, ios: AppPlatform.Ios, }; @@ -35,7 +38,7 @@ export async function fetchBuildsAsync({ }: { graphqlClient: ExpoGraphqlClient; projectId: string; - filters?: { statuses?: BuildStatus[]; platform?: string; profile?: string }; + filters?: { statuses?: BuildStatus[]; platform?: RequestedPlatform; profile?: string }; }): Promise { let builds: BuildFragment[]; const queryFilters: InputMaybe = {}; diff --git a/packages/eas-cli/src/commands/build/cancel.ts b/packages/eas-cli/src/commands/build/cancel.ts index 424daa573a..6f55b81500 100644 --- a/packages/eas-cli/src/commands/build/cancel.ts +++ b/packages/eas-cli/src/commands/build/cancel.ts @@ -14,6 +14,7 @@ import { } from '../../graphql/generated'; import Log from '../../log'; import { ora } from '../../ora'; +import { RequestedPlatform } from '../../platform'; import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils'; import { confirmAsync, selectAsync } from '../../prompts'; @@ -46,7 +47,7 @@ export async function selectBuildToCancelAsync( projectId: string, projectDisplayName: string, filters?: { - platform?: string; + platform?: RequestedPlatform; profile?: string; } ): Promise { @@ -96,10 +97,10 @@ export default class BuildCancel extends EasCommand { static override flags = { ...EASNonInteractiveFlag, - platform: Flags.string({ + platform: Flags.enum({ char: 'p', description: 'Filter builds by the platform if build ID is not provided', - options: ['android', 'ios', 'all'], + options: Object.values(RequestedPlatform), }), profile: Flags.string({ char: 'e', diff --git a/packages/eas-cli/src/commands/build/delete.ts b/packages/eas-cli/src/commands/build/delete.ts index 3e1cec9a4e..68afa11c02 100644 --- a/packages/eas-cli/src/commands/build/delete.ts +++ b/packages/eas-cli/src/commands/build/delete.ts @@ -9,6 +9,7 @@ import { withErrorHandlingAsync } from '../../graphql/client'; import { Build, DeleteBuildMutation, DeleteBuildMutationVariables } from '../../graphql/generated'; import Log from '../../log'; import { ora } from '../../ora'; +import { RequestedPlatform } from '../../platform'; import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils'; import { confirmAsync, selectAsync } from '../../prompts'; @@ -40,7 +41,7 @@ export async function selectBuildToDeleteAsync( projectId: string, projectDisplayName: string, filters?: { - platform?: string; + platform?: RequestedPlatform; profile?: string; } ): Promise { @@ -81,10 +82,10 @@ export default class BuildDelete extends EasCommand { static override args = [{ name: 'BUILD_ID' }]; static override flags = { ...EASNonInteractiveFlag, - platform: Flags.string({ + platform: Flags.enum({ char: 'p', description: 'Filter builds by the platform if build ID is not provided', - options: ['android', 'ios', 'all'], + options: Object.values(RequestedPlatform), }), profile: Flags.string({ char: 'e', From f2e1d0530da5745aa8bcd1bba84e560e1184d697 Mon Sep 17 00:00:00 2001 From: Radoslaw Krzemien Date: Tue, 16 Jan 2024 16:06:48 +0100 Subject: [PATCH 17/17] [eas-cli] Fix type Removed undefined See: https://linear.app/expo/issue/ENG-11026/add-eas-builddelete-command --- packages/eas-cli/src/commandUtils/builds.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eas-cli/src/commandUtils/builds.ts b/packages/eas-cli/src/commandUtils/builds.ts index efe3266b35..a8df1fa169 100644 --- a/packages/eas-cli/src/commandUtils/builds.ts +++ b/packages/eas-cli/src/commandUtils/builds.ts @@ -14,7 +14,7 @@ import { ExpoGraphqlClient } from './context/contextUtils/createGraphqlClient'; const platformToAppPlatform: Record< Exclude, - AppPlatform | undefined + AppPlatform > = { android: AppPlatform.Android, ios: AppPlatform.Ios,