diff --git a/CHANGELOG.md b/CHANGELOG.md index 84e5dbbf15..1379da5cc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,13 @@ This is the log of notable changes to EAS CLI and related packages. - [build-tools] Add `ios_signing_backend` option to the repack step. ([#4239](https://github.com/expo/eas-cli/pull/4239) by [@gabrieldonadel](https://github.com/gabrieldonadel)) - [build-tools] Support an optional `package_version` input on `eas/start_serve_sim_remote_session`, so a simulator session can pin the `@expo/serve-sim` version instead of always running `latest`. ([#4253](https://github.com/expo/eas-cli/pull/4253) by [@gwdp](https://github.com/gwdp)) +- [eas-cli] Display queued workflow runs: `workflow:run --wait` and `workflow:status` show a run waiting for its concurrency group and the run blocking it, and `workflow:cancel` includes queued runs in its selection list. ([#4258](https://github.com/expo/eas-cli/pull/4258) by [@hSATAC](https://github.com/hSATAC)) ### ๐Ÿ› Bug fixes +- [eas-cli] Stop reporting a finish time for workflow runs that have not finished: `workflow:runs` reports `finishedAt` only for runs in a final status. ([#4258](https://github.com/expo/eas-cli/pull/4258) by [@hSATAC](https://github.com/hSATAC)) +- [eas-cli] Honor the `--non-interactive` flag in `workflow:cancel`; it was parsed but never applied, so automation could hang on the interactive picker. ([#4258](https://github.com/expo/eas-cli/pull/4258) by [@hSATAC](https://github.com/hSATAC)) + ### ๐Ÿงน Chores ## [22.2.0](https://github.com/expo/eas-cli/releases/tag/v22.2.0) - 2026-08-20 diff --git a/packages/eas-cli/graphql.schema.json b/packages/eas-cli/graphql.schema.json index df470f677c..bb0d150db1 100644 --- a/packages/eas-cli/graphql.schema.json +++ b/packages/eas-cli/graphql.schema.json @@ -14158,6 +14158,35 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "customEvent", + "description": "A single custom event by the `id` of an `AppObserveCustomEvent`. Null when the app has no\nsuch event, including when it has aged out of retention.", + "args": [ + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "OBJECT", + "name": "AppObserveCustomEvent", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "customEventCounts", "description": null, @@ -14396,6 +14425,26 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "platforms", + "description": "Filter to these platforms. Cannot be set together with platform.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "AppObservePlatform", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "startTime", "description": null, @@ -14453,6 +14502,26 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "platforms", + "description": "Filter to these platforms. Cannot be set together with platform.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "AppObservePlatform", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "startTime", "description": null, @@ -14618,6 +14687,35 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "event", + "description": "A single metric event by the `id` of an `AppObserveEvent`. Null when the app has no such\nevent, including when it has aged out of retention or the id is not one this API issued.\n\n`sessionEventCount` and `userEventCount` are always null here: they are aggregates over a\ntime range, and a single event does not supply one.", + "args": [ + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "OBJECT", + "name": "AppObserveEvent", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "events", "description": null, @@ -15028,7 +15126,7 @@ }, { "name": "updates", - "description": null, + "description": "Update download activity (the Recent updates list). Not available on the free tier or legacy plans.", "args": [ { "name": "input", @@ -16394,12 +16492,28 @@ "name": "platform", "description": null, "type": { - "kind": "NON_NULL", + "kind": "ENUM", + "name": "AppObservePlatform", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "platforms", + "description": "Filter to these platforms. Cannot be set together with platform. One of platform or platforms is required.", + "type": { + "kind": "LIST", "name": null, "ofType": { - "kind": "ENUM", - "name": "AppObservePlatform", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "AppObservePlatform", + "ofType": null + } } }, "defaultValue": null, @@ -16659,6 +16773,26 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "platforms", + "description": "Filter to these platforms. Cannot be set together with platform.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "AppObservePlatform", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "propertyFilters", "description": null, @@ -17524,6 +17658,26 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "platforms", + "description": "Filter to these platforms. Cannot be set together with platform.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "AppObservePlatform", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "startTime", "description": null, @@ -17797,6 +17951,26 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "platforms", + "description": "Filter to these platforms. Cannot be set together with platform.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "AppObservePlatform", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "severity", "description": "Restrict to fatal-only or non-fatal-only errors.", @@ -18268,6 +18442,26 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "platforms", + "description": "Filter to these platforms. Cannot be set together with platform.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "AppObservePlatform", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "startTime", "description": null, @@ -18561,6 +18755,26 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "platforms", + "description": "Filter to these platforms. Cannot be set together with platform.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "AppObservePlatform", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "startTime", "description": null, @@ -19312,6 +19526,26 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "platforms", + "description": "Filter to these platforms. Cannot be set together with platform.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "AppObservePlatform", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "routeName", "description": null, @@ -19705,6 +19939,26 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "platforms", + "description": "Filter to these platforms. Cannot be set together with platform.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "AppObservePlatform", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "routeNames", "description": null, @@ -20094,6 +20348,26 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "platforms", + "description": "Filter to these platforms. Cannot be set together with platform.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "AppObservePlatform", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "startTime", "description": null, @@ -20470,6 +20744,26 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "platforms", + "description": "Filter to these platforms. Cannot be set together with platform.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "AppObservePlatform", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "startTime", "description": null, @@ -20510,7 +20804,7 @@ }, { "name": "downloadCount", - "description": "Downloads recorded in range across platforms; null when none were recorded (and always for the embedded bundle).", + "description": "Downloads recorded in range across platforms; null when none were recorded\n(and always for the embedded bundle). Download data is paid-only: on the\nfree tier and legacy plans this is always null.", "args": [], "type": { "kind": "SCALAR", @@ -20522,7 +20816,7 @@ }, { "name": "downloads", - "description": "One entry per platform with downloads in range; empty for the embedded bundle.", + "description": "One entry per platform with downloads in range; empty for the embedded bundle and on the free tier and legacy plans.", "args": [], "type": { "kind": "NON_NULL", @@ -21499,6 +21793,26 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "platforms", + "description": "Filter to these platforms. Cannot be set together with platform.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "AppObservePlatform", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "startTime", "description": null, @@ -21896,6 +22210,26 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "platforms", + "description": "Filter to these platforms. Cannot be set together with platform.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "AppObservePlatform", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "routeName", "description": null, @@ -22403,6 +22737,26 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "platforms", + "description": "Filter to these platforms. Cannot be set together with platform.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "AppObservePlatform", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "startTime", "description": null, @@ -37211,6 +37565,30 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "applicationArchiveUrl", + "description": "Application archive URL to download, install, and launch before the simulator session\nbecomes available. Mutually exclusive with buildId.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "buildId", + "description": "EAS Build to install and launch before the simulator session becomes available.\nMutually exclusive with applicationArchiveUrl.", + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "deviceIdentifier", "description": "Identifier of the virtual device to start for the session. On iOS this is a\nSimulator device name or UDID (e.g. \"iPhone 16 Pro\"). On Android this is an\nAVD hardware profile id (e.g. \"pixel_7\"). If omitted, the runner picks a\ndefault device.", @@ -42767,6 +43145,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "applicationArchiveUrl", + "description": "Direct application archive URL installed and launched for this session. Null when\nthe session uses an EAS Build or starts without an app.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "artifacts", "description": null, @@ -42791,6 +43181,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "build", + "description": "EAS Build installed and launched for this session. Null when the session uses\na direct application archive URL or starts without an app.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Build", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "createdAt", "description": null, @@ -74943,24 +75345,16 @@ "deprecationReason": null }, { - "name": "turtleBuildId", - "description": null, - "type": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "turtleJobRunId", + "name": "target", "description": null, "type": { - "kind": "SCALAR", - "name": "ID", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "TurtleSshTargetInput", + "ofType": null + } }, "defaultValue": null, "isDeprecated": false, @@ -75039,6 +75433,72 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "TurtleSshTargetInput", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "TurtleSshTargetType", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "TurtleSshTargetType", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "BUILD", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "JOB_RUN", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, { "kind": "ENUM", "name": "TurtleSshTransportType", @@ -94596,6 +95056,30 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "blockingWorkflowRun", + "description": "The run currently holding the concurrency group this run is waiting for.\nNull for runs that are not waiting, and briefly null while the group's\nnext run is being promoted.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "WorkflowRun", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cancelReason", + "description": "Why the server canceled this run. Null for manually canceled runs and for\nruns canceled before the reason was recorded.", + "args": [], + "type": { + "kind": "UNION", + "name": "WorkflowRunCancelReason", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "createdAt", "description": null, @@ -94960,6 +95444,45 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "UNION", + "name": "WorkflowRunCancelReason", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": [ + { + "kind": "OBJECT", + "name": "WorkflowRunCancelReasonConcurrencyGroup", + "ofType": null + } + ] + }, + { + "kind": "OBJECT", + "name": "WorkflowRunCancelReasonConcurrencyGroup", + "description": null, + "fields": [ + { + "name": "cancelingWorkflowRun", + "description": "The newer run in the same concurrency group whose creation canceled this run.\nNull if that run no longer exists.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "WorkflowRun", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "WorkflowRunEdge", @@ -95541,6 +96064,12 @@ "description": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "WAITING", + "description": null, + "isDeprecated": false, + "deprecationReason": null } ], "possibleTypes": null diff --git a/packages/eas-cli/src/__tests__/commands/utils.ts b/packages/eas-cli/src/__tests__/commands/utils.ts index f5fb7463a1..3a8df34573 100644 --- a/packages/eas-cli/src/__tests__/commands/utils.ts +++ b/packages/eas-cli/src/__tests__/commands/utils.ts @@ -191,6 +191,28 @@ export function getMockEmptyWorkflowRunsFragment(): WorkflowRunFragment[] { return getMockWorkflowRunsFragment(); } +export function getMockWorkflowRunFragment( + status: WorkflowRunStatus, + overrides?: Partial +): WorkflowRunFragment { + return { + id: `run-${status}`, + status, + triggerEventType: WorkflowRunTriggerEventType.Manual, + errors: [], + createdAt: '2022-01-01T00:00:00.000Z', + updatedAt: '2022-01-01T00:00:00.000Z', + gitCommitHash: '1234567890', + gitCommitMessage: 'commit message', + workflow: { + name: 'build', + id: 'build', + fileName: 'build.yml', + }, + ...overrides, + }; +} + export function getMockWorkflowRunsFragment( params?: | undefined @@ -233,6 +255,7 @@ export function getMockWorkflowRunsFragment( status: WorkflowRunStatus.Failure, createdAt: '2022-01-01T00:00:00.000Z', updatedAt: '2022-01-01T00:00:00.000Z', + finalizedAt: '2022-01-01T00:00:00.000Z', gitCommitHash: '1234567890', gitCommitMessage: 'commit message', workflow: { @@ -325,6 +348,7 @@ export function getMockWorkflowRunWithJobsFragment( id: mockProjectId, __typename: 'App', name: 'App', + slug: 'app', ownerAccount: { id: 'account-id', name: 'account-name', @@ -357,6 +381,7 @@ export function getMockWorkflowRunWithBuildJobsFragment( id: mockProjectId, __typename: 'App', name: 'App', + slug: 'app', ownerAccount: { id: 'account-id', name: 'account-name', diff --git a/packages/eas-cli/src/__tests__/commands/workflow-cancel-test.ts b/packages/eas-cli/src/__tests__/commands/workflow-cancel-test.ts new file mode 100644 index 0000000000..81cc108010 --- /dev/null +++ b/packages/eas-cli/src/__tests__/commands/workflow-cancel-test.ts @@ -0,0 +1,87 @@ +import { + getMockWorkflowRunFragment, + mockCommandContext, + mockProjectId, + mockTestCommand, +} from './utils'; +import WorkflowRunCancel from '../../commands/workflow/cancel'; +import { WorkflowRunStatus } from '../../graphql/generated'; +import { WorkflowRunMutation } from '../../graphql/mutations/WorkflowRunMutation'; +import { AppQuery } from '../../graphql/queries/AppQuery'; +import { promptAsync } from '../../prompts'; + +jest.mock('../../graphql/queries/AppQuery'); +jest.mock('../../graphql/mutations/WorkflowRunMutation'); +jest.mock('../../log'); +jest.mock('../../prompts'); + +describe(WorkflowRunCancel, () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('prompts with in-progress and queued runs and cancels the selected ones', async () => { + const ctx = mockCommandContext(WorkflowRunCancel, { + projectId: mockProjectId, + }); + const inProgressRun = getMockWorkflowRunFragment(WorkflowRunStatus.InProgress); + const waitingRun = getMockWorkflowRunFragment(WorkflowRunStatus.Waiting); + jest + .mocked(AppQuery.byIdWorkflowRunsFilteredByStatusAsync) + .mockImplementation(async (_graphqlClient, _appId, status) => + status === WorkflowRunStatus.InProgress ? [inProgressRun] : [waitingRun] + ); + jest.mocked(promptAsync).mockResolvedValueOnce({ selectedRuns: [waitingRun.id] }); + + const cmd = mockTestCommand(WorkflowRunCancel, [], ctx); + await cmd.run(); + + expect(AppQuery.byIdWorkflowRunsFilteredByStatusAsync).toHaveBeenCalledWith( + ctx.loggedIn.graphqlClient, + mockProjectId, + WorkflowRunStatus.InProgress, + 50 + ); + expect(AppQuery.byIdWorkflowRunsFilteredByStatusAsync).toHaveBeenCalledWith( + ctx.loggedIn.graphqlClient, + mockProjectId, + WorkflowRunStatus.Waiting, + 50 + ); + const promptCall = jest.mocked(promptAsync).mock.calls[0][0] as any; + expect(promptCall.choices.map((choice: any) => choice.value)).toEqual([ + inProgressRun.id, + waitingRun.id, + ]); + expect(WorkflowRunMutation.cancelWorkflowRunAsync).toHaveBeenCalledTimes(1); + expect(WorkflowRunMutation.cancelWorkflowRunAsync).toHaveBeenCalledWith( + ctx.loggedIn.graphqlClient, + { workflowRunId: waitingRun.id } + ); + }); + + test('throws in non-interactive mode when no run IDs are given', async () => { + const ctx = mockCommandContext(WorkflowRunCancel, { + projectId: mockProjectId, + }); + const cmd = mockTestCommand(WorkflowRunCancel, ['--non-interactive'], ctx); + await expect(cmd.run()).rejects.toThrow( + 'Must supply workflow run IDs as arguments when in non-interactive mode' + ); + expect(promptAsync).not.toHaveBeenCalled(); + }); + + test('cancels the given run IDs without prompting in non-interactive mode', async () => { + const ctx = mockCommandContext(WorkflowRunCancel, { + projectId: mockProjectId, + }); + const cmd = mockTestCommand(WorkflowRunCancel, ['--non-interactive', 'run-id-1'], ctx); + await cmd.run(); + expect(AppQuery.byIdWorkflowRunsFilteredByStatusAsync).not.toHaveBeenCalled(); + expect(promptAsync).not.toHaveBeenCalled(); + expect(WorkflowRunMutation.cancelWorkflowRunAsync).toHaveBeenCalledWith( + ctx.loggedIn.graphqlClient, + { workflowRunId: 'run-id-1' } + ); + }); +}); diff --git a/packages/eas-cli/src/__tests__/commands/workflow-runs-test.ts b/packages/eas-cli/src/__tests__/commands/workflow-runs-test.ts index 502ca435ad..fc7af2e8f0 100644 --- a/packages/eas-cli/src/__tests__/commands/workflow-runs-test.ts +++ b/packages/eas-cli/src/__tests__/commands/workflow-runs-test.ts @@ -142,6 +142,7 @@ describe(WorkflowRunList, () => { triggerEventType: WorkflowRunTriggerEventType.Manual, createdAt: '2022-01-01T00:00:00.000Z', updatedAt: '2022-01-01T00:00:00.000Z', + finalizedAt: '2022-01-01T00:00:00.000Z', gitCommitHash: '1234567890', gitCommitMessage: 'commit message', errors: [], diff --git a/packages/eas-cli/src/commandUtils/workflow/__tests__/utils-test.ts b/packages/eas-cli/src/commandUtils/workflow/__tests__/utils-test.ts index e93555ecee..87ba88805e 100644 --- a/packages/eas-cli/src/commandUtils/workflow/__tests__/utils-test.ts +++ b/packages/eas-cli/src/commandUtils/workflow/__tests__/utils-test.ts @@ -1,7 +1,10 @@ -import { getMockWorkflowRunWithJobsFragment } from '../../../__tests__/commands/utils'; +import { + getMockWorkflowRunFragment, + getMockWorkflowRunWithJobsFragment, +} from '../../../__tests__/commands/utils'; import { fetchRawLogsForCustomJobAsync } from '../fetchLogs'; -import { infoForActiveWorkflowRunAsync } from '../utils'; -import { WorkflowJobStatus } from '../../../graphql/generated'; +import { infoForActiveWorkflowRunAsync, processWorkflowRuns } from '../utils'; +import { WorkflowJobStatus, WorkflowRunStatus } from '../../../graphql/generated'; jest.mock('../fetchLogs'); @@ -33,3 +36,32 @@ describe('workflow utils', () => { expect(output).not.toContain('step-id-1'); }); }); + +describe(processWorkflowRuns, () => { + test.each([WorkflowRunStatus.Success, WorkflowRunStatus.Failure, WorkflowRunStatus.Canceled])( + 'reports finalizedAt as finishedAt for final status %s', + status => { + const [run] = processWorkflowRuns([ + getMockWorkflowRunFragment(status, { finalizedAt: '2022-01-01T12:00:00.000Z' }), + ]); + expect(run.finishedAt).toBe('2022-01-01T12:00:00.000Z'); + } + ); + + test('reports no finishedAt for a final run predating finalizedAt', () => { + const [run] = processWorkflowRuns([getMockWorkflowRunFragment(WorkflowRunStatus.Success)]); + expect(run.finishedAt).toBeNull(); + }); + + test.each([ + WorkflowRunStatus.New, + WorkflowRunStatus.Waiting, + WorkflowRunStatus.InProgress, + WorkflowRunStatus.ActionRequired, + ])('reports no finishedAt for non-final status %s even with a lingering finalizedAt', status => { + const [run] = processWorkflowRuns([ + getMockWorkflowRunFragment(status, { finalizedAt: '2022-01-01T12:00:00.000Z' }), + ]); + expect(run.finishedAt).toBeNull(); + }); +}); diff --git a/packages/eas-cli/src/commandUtils/workflow/types.ts b/packages/eas-cli/src/commandUtils/workflow/types.ts index b2897639b2..1433feb4f3 100644 --- a/packages/eas-cli/src/commandUtils/workflow/types.ts +++ b/packages/eas-cli/src/commandUtils/workflow/types.ts @@ -19,7 +19,7 @@ export type WorkflowRunResult = { triggerType: WorkflowTriggerType; trigger: string | null; startedAt: string; - finishedAt: string; + finishedAt: string | null; workflowId: string; workflowName: string | null; workflowFileName: string; diff --git a/packages/eas-cli/src/commandUtils/workflow/utils.ts b/packages/eas-cli/src/commandUtils/workflow/utils.ts index 589d76dd9f..fb29e80d24 100644 --- a/packages/eas-cli/src/commandUtils/workflow/utils.ts +++ b/packages/eas-cli/src/commandUtils/workflow/utils.ts @@ -2,6 +2,7 @@ import chalk from 'chalk'; import * as fs from 'node:fs'; import { fetchRawLogsForBuildJobAsync, fetchRawLogsForCustomJobAsync } from './fetchLogs'; +import { getWorkflowRunUrl } from '../../build/utils/url'; import { WorkflowJobResult, WorkflowLogLine, @@ -102,8 +103,15 @@ export function choicesFromWorkflowLogs( } export function processWorkflowRuns(runs: WorkflowRunFragment[]): WorkflowRunResult[] { + const finalWorkflowRunStatuses = new Set([ + WorkflowRunStatus.Success, + WorkflowRunStatus.Failure, + WorkflowRunStatus.Canceled, + ]); return runs.map(run => { - const finishedAt = run.status === WorkflowRunStatus.InProgress ? null : run.updatedAt; + // finalizedAt can linger on a run an admin forced back out of a final status, + // so gate on the status rather than trusting the field alone. + const finishedAt = finalWorkflowRunStatuses.has(run.status) ? (run.finalizedAt ?? null) : null; const { triggerType, trigger } = computeTriggerInfoForWorkflowRun(run); return { id: run.id, @@ -326,7 +334,29 @@ export async function showWorkflowStatusAsync( switch (workflowRun.status) { case WorkflowRunStatus.New: + // A run can reach NEW from WAITING (promoted out of its concurrency + // group), so reset the waiting-state text. + spinner.prefixText = chalk`{bold.yellow Workflow run is waiting to start:}`; + spinner.text = ''; + break; + case WorkflowRunStatus.Waiting: { + spinner.prefixText = chalk`{bold.yellow Workflow run is queued:}`; + const blockingWorkflowRun = workflowRun.blockingWorkflowRun; + // The blocking run is in the same app: concurrency groups are app-scoped. + const app = workflowRun.workflow.app; + const actionRequiredSuffix = + blockingWorkflowRun?.status === WorkflowRunStatus.ActionRequired + ? ` โ€” it needs your action first: ${getWorkflowRunUrl( + app.ownerAccount.name, + app.slug, + blockingWorkflowRun.id + )}` + : ''; + spinner.text = blockingWorkflowRun + ? `Waiting for ${blockingWorkflowRun.name}${actionRequiredSuffix}` + : 'Waiting for its concurrency group'; break; + } case WorkflowRunStatus.InProgress: { spinner.prefixText = chalk`{bold.green Workflow run is in progress:}`; spinner.text = await infoForActiveWorkflowRunAsync(graphqlClient, workflowRun, 5); @@ -334,10 +364,12 @@ export async function showWorkflowStatusAsync( } case WorkflowRunStatus.ActionRequired: spinner.prefixText = chalk`{bold.yellow Workflow run is waiting for action:}`; + spinner.text = ''; break; case WorkflowRunStatus.Canceled: spinner.prefixText = chalk`{bold.yellow Workflow has been canceled.}`; + spinner.text = ''; spinner.stopAndPersist(); return workflowRun; diff --git a/packages/eas-cli/src/commands/workflow/cancel.ts b/packages/eas-cli/src/commands/workflow/cancel.ts index 83f503a930..b3e3b7a534 100644 --- a/packages/eas-cli/src/commands/workflow/cancel.ts +++ b/packages/eas-cli/src/commands/workflow/cancel.ts @@ -9,7 +9,7 @@ import { promptAsync } from '../../prompts'; export default class WorkflowRunCancel extends EasCommand { static override description = - 'Cancel one or more workflow runs. If no workflow run IDs are provided, you will be prompted to select IN_PROGRESS runs to cancel.'; + 'Cancel one or more workflow runs. If no workflow run IDs are provided, you will be prompted to select in-progress or queued runs to cancel.'; static override strict = false; @@ -22,21 +22,11 @@ export default class WorkflowRunCancel extends EasCommand { }; async runAsync(): Promise { - const { argv } = await this.parse(WorkflowRunCancel); - let nonInteractive = false; - const workflowRunIds: Set = new Set(); - - // Custom parsing of argv - const tokens = [...argv] as string[]; - while (tokens.length > 0) { - const token = tokens.shift(); - if (token === '--non-interactive') { - nonInteractive = true; - continue; - } else if (token) { - workflowRunIds.add(token); - } - } + const { argv, flags } = await this.parse(WorkflowRunCancel); + // strict = false, so argv holds the variadic run IDs; oclif parses known + // flags like --non-interactive out of argv. + const nonInteractive = flags['non-interactive']; + const workflowRunIds = new Set(argv as string[]); const { projectId, @@ -49,14 +39,14 @@ export default class WorkflowRunCancel extends EasCommand { if (nonInteractive) { throw new Error('Must supply workflow run IDs as arguments when in non-interactive mode'); } - // Run the workflow run list query and select runs to cancel - const queryResult = await AppQuery.byIdWorkflowRunsFilteredByStatusAsync( - graphqlClient, - projectId, - WorkflowRunStatus.InProgress, - 50 + // The runs filter takes a single status, so query each picker status separately. + const pickerStatuses = [WorkflowRunStatus.InProgress, WorkflowRunStatus.Waiting]; + const runsByStatus = await Promise.all( + pickerStatuses.map(status => + AppQuery.byIdWorkflowRunsFilteredByStatusAsync(graphqlClient, projectId, status, 50) + ) ); - const runs = processWorkflowRuns(queryResult); + const runs = processWorkflowRuns(runsByStatus.flat()); if (runs.length === 0) { Log.warn('No workflow runs to cancel'); return; @@ -64,7 +54,7 @@ export default class WorkflowRunCancel extends EasCommand { const answers = await promptAsync({ type: 'multiselect', name: 'selectedRuns', - message: 'Select IN_PROGRESS workflow runs to cancel', + message: 'Select workflow runs to cancel', choices: runs.map(run => choiceFromWorkflowRun(run)), }); answers.selectedRuns.forEach((id: string) => { diff --git a/packages/eas-cli/src/commands/workflow/runs.ts b/packages/eas-cli/src/commands/workflow/runs.ts index d0463034b3..57c034821a 100644 --- a/packages/eas-cli/src/commands/workflow/runs.ts +++ b/packages/eas-cli/src/commands/workflow/runs.ts @@ -84,7 +84,7 @@ export default class WorkflowRunList extends EasCommand { { label: 'Workflow', value: run.workflowFileName }, { label: 'Status', value: run.status }, { label: 'Started At', value: run.startedAt }, - { label: 'Finished At', value: run.finishedAt }, + { label: 'Finished At', value: run.finishedAt ?? 'null' }, { label: 'Trigger Type', value: run.triggerType }, { label: 'Trigger', value: run.trigger ?? 'null' }, { label: 'Git Commit Message', value: run.gitCommitMessage ?? 'null' }, diff --git a/packages/eas-cli/src/graphql/generated.ts b/packages/eas-cli/src/graphql/generated.ts index ac2f996704..e7c3db83d6 100644 --- a/packages/eas-cli/src/graphql/generated.ts +++ b/packages/eas-cli/src/graphql/generated.ts @@ -2173,6 +2173,11 @@ export type AppNotificationPreferenceInput = { export type AppObserve = { __typename?: 'AppObserve'; appVersions: Array; + /** + * A single custom event by the `id` of an `AppObserveCustomEvent`. Null when the app has no + * such event, including when it has aged out of retention. + */ + customEvent?: Maybe; customEventCounts: AppObserveCustomEventCounts; customEventList: AppObserveCustomEventListConnection; customEventNames: AppObserveCustomEventNames; @@ -2185,6 +2190,14 @@ export type AppObserve = { errorStats: AppObserveErrorStats; /** Time-bucketed exception counts split into fatal vs non-fatal, for the stacked-bar chart. */ errorTimeSeries: AppObserveErrorTimeSeries; + /** + * A single metric event by the `id` of an `AppObserveEvent`. Null when the app has no such + * event, including when it has aged out of retention or the id is not one this API issued. + * + * `sessionEventCount` and `userEventCount` are always null here: they are aggregates over a + * time range, and a single event does not supply one. + */ + event?: Maybe; events: AppObserveEventsConnection; /** Highest Expo SDK version seen in telemetry over the trailing 30 days; null when none was reported. */ latestExpoSdkVersion?: Maybe; @@ -2208,6 +2221,7 @@ export type AppObserve = { * universe used by `appVersions` headline counts. */ uniqueActiveUserCount: Scalars['Int']['output']; + /** Update download activity (the Recent updates list). Not available on the free tier or legacy plans. */ updates: AppObserveUpdatesConnection; }; @@ -2217,6 +2231,11 @@ export type AppObserveAppVersionsArgs = { }; +export type AppObserveCustomEventArgs = { + id: Scalars['ID']['input']; +}; + + export type AppObserveCustomEventCountsArgs = { input: AppObserveCustomEventCountsInput; }; @@ -2242,6 +2261,7 @@ export type AppObserveCustomEventNamesArgs = { isEmbeddedUpdate?: InputMaybe; orderBy?: InputMaybe; platform?: InputMaybe; + platforms?: InputMaybe>; startTime: Scalars['DateTime']['input']; }; @@ -2249,6 +2269,7 @@ export type AppObserveCustomEventNamesArgs = { export type AppObserveEnvironmentsArgs = { endTime?: InputMaybe; platform?: InputMaybe; + platforms?: InputMaybe>; startTime?: InputMaybe; }; @@ -2273,6 +2294,11 @@ export type AppObserveErrorTimeSeriesArgs = { }; +export type AppObserveEventArgs = { + id: Scalars['ID']['input']; +}; + + export type AppObserveEventsArgs = { after?: InputMaybe; before?: InputMaybe; @@ -2479,7 +2505,9 @@ export type AppObserveCustomEventCountsInput = { environment?: InputMaybe; eventName: Scalars['String']['input']; isEmbeddedUpdate?: InputMaybe; - platform: AppObservePlatform; + platform?: InputMaybe; + /** Filter to these platforms. Cannot be set together with platform. One of platform or platforms is required. */ + platforms?: InputMaybe>; startTime: Scalars['DateTime']['input']; }; @@ -2508,6 +2536,8 @@ export type AppObserveCustomEventListFilter = { eventName?: InputMaybe; isEmbeddedUpdate?: InputMaybe; platform?: InputMaybe; + /** Filter to these platforms. Cannot be set together with platform. */ + platforms?: InputMaybe>; propertyFilters?: InputMaybe>; sessionId?: InputMaybe; startTime?: InputMaybe; @@ -2615,6 +2645,8 @@ export type AppObserveErrorGroupBreakdownInput = { fingerprint: Scalars['String']['input']; isEmbeddedUpdate?: InputMaybe; platform?: InputMaybe; + /** Filter to these platforms. Cannot be set together with platform. */ + platforms?: InputMaybe>; startTime: Scalars['DateTime']['input']; }; @@ -2647,6 +2679,8 @@ export type AppObserveErrorGroupsInput = { isEmbeddedUpdate?: InputMaybe; orderBy?: InputMaybe; platform?: InputMaybe; + /** Filter to these platforms. Cannot be set together with platform. */ + platforms?: InputMaybe>; /** Restrict to fatal-only or non-fatal-only errors. */ severity?: InputMaybe; /** Restrict to a capture source (e.g. global). */ @@ -2710,6 +2744,8 @@ export type AppObserveErrorStatsInput = { environment?: InputMaybe; isEmbeddedUpdate?: InputMaybe; platform?: InputMaybe; + /** Filter to these platforms. Cannot be set together with platform. */ + platforms?: InputMaybe>; startTime: Scalars['DateTime']['input']; }; @@ -2746,6 +2782,8 @@ export type AppObserveErrorTimeSeriesInput = { fingerprint?: InputMaybe; isEmbeddedUpdate?: InputMaybe; platform?: InputMaybe; + /** Filter to these platforms. Cannot be set together with platform. */ + platforms?: InputMaybe>; startTime: Scalars['DateTime']['input']; }; @@ -2817,6 +2855,8 @@ export type AppObserveEventsFilter = { isEmbeddedUpdate?: InputMaybe; metricName?: InputMaybe; platform?: InputMaybe; + /** Filter to these platforms. Cannot be set together with platform. */ + platforms?: InputMaybe>; routeName?: InputMaybe; sessionId?: InputMaybe; startTime?: InputMaybe; @@ -2871,6 +2911,8 @@ export type AppObserveNavigationRoutesFilter = { endTime: Scalars['DateTime']['input']; environment?: InputMaybe; platform?: InputMaybe; + /** Filter to these platforms. Cannot be set together with platform. */ + platforms?: InputMaybe>; routeNames?: InputMaybe>; startTime: Scalars['DateTime']['input']; }; @@ -2924,6 +2966,8 @@ export type AppObserveOverviewEngagementInput = { endTime: Scalars['DateTime']['input']; environment?: InputMaybe; platform?: InputMaybe; + /** Filter to these platforms. Cannot be set together with platform. */ + platforms?: InputMaybe>; startTime: Scalars['DateTime']['input']; }; @@ -2971,6 +3015,8 @@ export type AppObserveOverviewStabilityInput = { endTime: Scalars['DateTime']['input']; environment?: InputMaybe; platform?: InputMaybe; + /** Filter to these platforms. Cannot be set together with platform. */ + platforms?: InputMaybe>; startTime: Scalars['DateTime']['input']; }; @@ -2978,9 +3024,13 @@ export type AppObserveOverviewUpdate = { __typename?: 'AppObserveOverviewUpdate'; /** Null for the embedded bundle. */ appUpdateId?: Maybe; - /** Downloads recorded in range across platforms; null when none were recorded (and always for the embedded bundle). */ + /** + * Downloads recorded in range across platforms; null when none were recorded + * (and always for the embedded bundle). Download data is paid-only: on the + * free tier and legacy plans this is always null. + */ downloadCount?: Maybe; - /** One entry per platform with downloads in range; empty for the embedded bundle. */ + /** One entry per platform with downloads in range; empty for the embedded bundle and on the free tier and legacy plans. */ downloads: Array; eventCount: Scalars['Int']['output']; firstSeenAt: Scalars['DateTime']['output']; @@ -3092,6 +3142,8 @@ export type AppObserveReleasesInput = { environment?: InputMaybe; metricNames?: InputMaybe>; platform?: InputMaybe; + /** Filter to these platforms. Cannot be set together with platform. */ + platforms?: InputMaybe>; startTime: Scalars['DateTime']['input']; }; @@ -3129,6 +3181,8 @@ export type AppObserveTimeSeriesInput = { isEmbeddedUpdate?: InputMaybe; metricName: Scalars['String']['input']; platform?: InputMaybe; + /** Filter to these platforms. Cannot be set together with platform. */ + platforms?: InputMaybe>; routeName?: InputMaybe; startTime: Scalars['DateTime']['input']; }; @@ -3180,6 +3234,8 @@ export type AppObserveUpdatesInput = { first?: InputMaybe; orderBy?: InputMaybe; platform?: InputMaybe; + /** Filter to these platforms. Cannot be set together with platform. */ + platforms?: InputMaybe>; startTime: Scalars['DateTime']['input']; }; @@ -5234,6 +5290,16 @@ export type CreateDeviceRunSessionEventLogUploadSessionResult = { export type CreateDeviceRunSessionInput = { appId: Scalars['ID']['input']; + /** + * Application archive URL to download, install, and launch before the simulator session + * becomes available. Mutually exclusive with buildId. + */ + applicationArchiveUrl?: InputMaybe; + /** + * EAS Build to install and launch before the simulator session becomes available. + * Mutually exclusive with applicationArchiveUrl. + */ + buildId?: InputMaybe; /** * Identifier of the virtual device to start for the session. On iOS this is a * Simulator device name or UDID (e.g. "iPhone 16 Pro"). On Android this is an @@ -6012,7 +6078,17 @@ export type DeploymentsMutationDeleteWorkerDeploymentByIdentifierArgs = { export type DeviceRunSession = { __typename?: 'DeviceRunSession'; app: App; + /** + * Direct application archive URL installed and launched for this session. Null when + * the session uses an EAS Build or starts without an app. + */ + applicationArchiveUrl?: Maybe; artifacts: Array; + /** + * EAS Build installed and launched for this session. Null when the session uses + * a direct application archive URL or starts without an app. + */ + build?: Maybe; createdAt: Scalars['DateTime']['output']; finishedAt?: Maybe; id: Scalars['ID']['output']; @@ -10613,8 +10689,7 @@ export type TurtleSshSessionMutation = { export type TurtleSshSessionMutationCreateOrUpdateTurtleSshSessionArgs = { connectionConfig: TurtleSshConnectionConfigInput; sessionSettings: TurtleSshSessionSettingsInput; - turtleBuildId?: InputMaybe; - turtleJobRunId?: InputMaybe; + target: TurtleSshTargetInput; }; export type TurtleSshSessionSettings = { @@ -10626,6 +10701,16 @@ export type TurtleSshSessionSettingsInput = { idleTimeoutSeconds: Scalars['Int']['input']; }; +export type TurtleSshTargetInput = { + id: Scalars['ID']['input']; + type: TurtleSshTargetType; +}; + +export enum TurtleSshTargetType { + Build = 'BUILD', + JobRun = 'JOB_RUN' +} + export enum TurtleSshTransportType { UptermV1 = 'UPTERM_V1' } @@ -13246,6 +13331,17 @@ export type WorkflowRun = ActivityTimelineProjectActivity & { __typename?: 'WorkflowRun'; activityTimestamp: Scalars['DateTime']['output']; actor?: Maybe; + /** + * The run currently holding the concurrency group this run is waiting for. + * Null for runs that are not waiting, and briefly null while the group's + * next run is being promoted. + */ + blockingWorkflowRun?: Maybe; + /** + * Why the server canceled this run. Null for manually canceled runs and for + * runs canceled before the reason was recorded. + */ + cancelReason?: Maybe; createdAt: Scalars['DateTime']['output']; durationSeconds?: Maybe; errors: Array; @@ -13272,6 +13368,17 @@ export type WorkflowRun = ActivityTimelineProjectActivity & { workflowRevision?: Maybe; }; +export type WorkflowRunCancelReason = WorkflowRunCancelReasonConcurrencyGroup; + +export type WorkflowRunCancelReasonConcurrencyGroup = { + __typename?: 'WorkflowRunCancelReasonConcurrencyGroup'; + /** + * The newer run in the same concurrency group whose creation canceled this run. + * Null if that run no longer exists. + */ + cancelingWorkflowRun?: Maybe; +}; + export type WorkflowRunEdge = { __typename?: 'WorkflowRunEdge'; cursor: Scalars['String']['output']; @@ -13361,7 +13468,8 @@ export enum WorkflowRunStatus { Failure = 'FAILURE', InProgress = 'IN_PROGRESS', New = 'NEW', - Success = 'SUCCESS' + Success = 'SUCCESS', + Waiting = 'WAITING' } export type WorkflowRunTimeRangeInput = { @@ -14716,7 +14824,7 @@ export type AppByIdWorkflowRunsFilteredByStatusQueryVariables = Exact<{ }>; -export type AppByIdWorkflowRunsFilteredByStatusQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, runs: { __typename?: 'AppWorkflowRunsConnection', edges: Array<{ __typename?: 'AppWorkflowRunEdge', node: { __typename?: 'WorkflowRun', id: string, status: WorkflowRunStatus, gitCommitMessage?: string | null, gitCommitHash?: string | null, requestedGitRef?: string | null, triggeringLabelName?: string | null, triggerEventType: WorkflowRunTriggerEventType, triggeringSchedule?: string | null, createdAt: any, updatedAt: any, actor?: +export type AppByIdWorkflowRunsFilteredByStatusQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, runs: { __typename?: 'AppWorkflowRunsConnection', edges: Array<{ __typename?: 'AppWorkflowRunEdge', node: { __typename?: 'WorkflowRun', id: string, status: WorkflowRunStatus, gitCommitMessage?: string | null, gitCommitHash?: string | null, requestedGitRef?: string | null, triggeringLabelName?: string | null, triggerEventType: WorkflowRunTriggerEventType, triggeringSchedule?: string | null, createdAt: any, updatedAt: any, finalizedAt?: any | null, actor?: | { __typename: 'PartnerActor', id: string } | { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } @@ -15415,7 +15523,7 @@ export type WorkflowRunByIdWithJobsQueryVariables = Exact<{ }>; -export type WorkflowRunByIdWithJobsQuery = { __typename?: 'RootQuery', workflowRuns: { __typename?: 'WorkflowRunQuery', byId: { __typename?: 'WorkflowRun', id: string, status: WorkflowRunStatus, gitCommitMessage?: string | null, gitCommitHash?: string | null, requestedGitRef?: string | null, triggeringLabelName?: string | null, triggerEventType: WorkflowRunTriggerEventType, triggeringSchedule?: string | null, createdAt: any, updatedAt: any, workflow: { __typename?: 'Workflow', id: string, name?: string | null, fileName: string, app: { __typename?: 'App', id: string, name: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } }, jobs: Array<{ __typename?: 'WorkflowJob', id: string, key: string, name: string, status: WorkflowJobStatus, type: WorkflowJobType, outputs: any, createdAt: any, updatedAt: any, workflowRun: { __typename?: 'WorkflowRun', id: string }, turtleJobRun?: { __typename?: 'JobRun', id: string, logFileUrls: Array, artifacts: Array<{ __typename?: 'WorkflowArtifact', id: string, name: string, contentType?: string | null, fileSizeBytes?: number | null, filename: string, downloadUrl?: string | null }>, errors: Array<{ __typename?: 'JobRunError', errorCode: string, message: string }> } | null, turtleBuild?: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, logFiles: Array, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, appIdentifier?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string } | null, initiatingActor?: +export type WorkflowRunByIdWithJobsQuery = { __typename?: 'RootQuery', workflowRuns: { __typename?: 'WorkflowRunQuery', byId: { __typename?: 'WorkflowRun', id: string, status: WorkflowRunStatus, gitCommitMessage?: string | null, gitCommitHash?: string | null, requestedGitRef?: string | null, triggeringLabelName?: string | null, triggerEventType: WorkflowRunTriggerEventType, triggeringSchedule?: string | null, createdAt: any, updatedAt: any, finalizedAt?: any | null, blockingWorkflowRun?: { __typename?: 'WorkflowRun', id: string, name: string, status: WorkflowRunStatus } | null, workflow: { __typename?: 'Workflow', id: string, name?: string | null, fileName: string, app: { __typename?: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } }, jobs: Array<{ __typename?: 'WorkflowJob', id: string, key: string, name: string, status: WorkflowJobStatus, type: WorkflowJobType, outputs: any, createdAt: any, updatedAt: any, workflowRun: { __typename?: 'WorkflowRun', id: string }, turtleJobRun?: { __typename?: 'JobRun', id: string, logFileUrls: Array, artifacts: Array<{ __typename?: 'WorkflowArtifact', id: string, name: string, contentType?: string | null, fileSizeBytes?: number | null, filename: string, downloadUrl?: string | null }>, errors: Array<{ __typename?: 'JobRunError', errorCode: string, message: string }> } | null, turtleBuild?: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, logFiles: Array, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, appIdentifier?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string } | null, initiatingActor?: | { __typename: 'PartnerActor', id: string, displayName: string } | { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } @@ -15435,7 +15543,7 @@ export type WorkflowRunsForAppIdFileNameAndStatusQueryVariables = Exact<{ }>; -export type WorkflowRunsForAppIdFileNameAndStatusQuery = { __typename?: 'RootQuery', workflows: { __typename?: 'WorkflowQuery', byAppIdAndFileName: { __typename?: 'Workflow', id: string, runs: { __typename?: 'WorkflowRunsConnection', edges: Array<{ __typename?: 'WorkflowRunEdge', node: { __typename?: 'WorkflowRun', id: string, status: WorkflowRunStatus, gitCommitMessage?: string | null, gitCommitHash?: string | null, requestedGitRef?: string | null, triggeringLabelName?: string | null, triggerEventType: WorkflowRunTriggerEventType, triggeringSchedule?: string | null, createdAt: any, updatedAt: any, actor?: +export type WorkflowRunsForAppIdFileNameAndStatusQuery = { __typename?: 'RootQuery', workflows: { __typename?: 'WorkflowQuery', byAppIdAndFileName: { __typename?: 'Workflow', id: string, runs: { __typename?: 'WorkflowRunsConnection', edges: Array<{ __typename?: 'WorkflowRunEdge', node: { __typename?: 'WorkflowRun', id: string, status: WorkflowRunStatus, gitCommitMessage?: string | null, gitCommitHash?: string | null, requestedGitRef?: string | null, triggeringLabelName?: string | null, triggerEventType: WorkflowRunTriggerEventType, triggeringSchedule?: string | null, createdAt: any, updatedAt: any, finalizedAt?: any | null, actor?: | { __typename: 'PartnerActor', id: string } | { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } @@ -15576,7 +15684,7 @@ export type WorkflowJobFragment = { __typename?: 'WorkflowJob', id: string, key: | { __typename: 'User', id: string, displayName: string } | null, app: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, updateChannel?: { __typename?: 'UpdateChannel', id: string, name: string } | null, runtime?: { __typename?: 'Runtime', id: string, version: string } | null, metrics?: { __typename?: 'BuildMetrics', buildWaitTime?: number | null, buildQueueTime?: number | null, buildDuration?: number | null } | null } | null, errors: Array<{ __typename?: 'WorkflowJobError', title: string, message: string }> }; -export type WorkflowRunFragment = { __typename?: 'WorkflowRun', id: string, status: WorkflowRunStatus, gitCommitMessage?: string | null, gitCommitHash?: string | null, requestedGitRef?: string | null, triggeringLabelName?: string | null, triggerEventType: WorkflowRunTriggerEventType, triggeringSchedule?: string | null, createdAt: any, updatedAt: any, actor?: +export type WorkflowRunFragment = { __typename?: 'WorkflowRun', id: string, status: WorkflowRunStatus, gitCommitMessage?: string | null, gitCommitHash?: string | null, requestedGitRef?: string | null, triggeringLabelName?: string | null, triggerEventType: WorkflowRunTriggerEventType, triggeringSchedule?: string | null, createdAt: any, updatedAt: any, finalizedAt?: any | null, actor?: | { __typename: 'PartnerActor', id: string } | { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } diff --git a/packages/eas-cli/src/graphql/queries/WorkflowRunQuery.ts b/packages/eas-cli/src/graphql/queries/WorkflowRunQuery.ts index 0cde37fb2c..a82f0a2b4d 100644 --- a/packages/eas-cli/src/graphql/queries/WorkflowRunQuery.ts +++ b/packages/eas-cli/src/graphql/queries/WorkflowRunQuery.ts @@ -118,11 +118,17 @@ export const WorkflowRunQuery = { workflowRuns { byId(workflowRunId: $workflowRunId) { id + blockingWorkflowRun { + id + name + status + } workflow { id app { id name + slug ownerAccount { id name diff --git a/packages/eas-cli/src/graphql/types/WorkflowRun.ts b/packages/eas-cli/src/graphql/types/WorkflowRun.ts index 1e8f9e248b..f0b1cf4907 100644 --- a/packages/eas-cli/src/graphql/types/WorkflowRun.ts +++ b/packages/eas-cli/src/graphql/types/WorkflowRun.ts @@ -22,6 +22,7 @@ export const WorkflowRunFragmentNode = gql` triggeringSchedule createdAt updatedAt + finalizedAt errors { title message