diff --git a/CHANGELOG.md b/CHANGELOG.md index 932d2f8d44..bc7e15b244 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This is the log of notable changes to EAS CLI and related packages. - Make automatic env resolution message shorter. ([#2806](https://github.com/expo/eas-cli/pull/2806) by [@szdziedzic](https://github.com/szdziedzic)) - Make "No remote versions are configured" message green instead of yellow. ([#2805](https://github.com/expo/eas-cli/pull/2805) by [@szdziedzic](https://github.com/szdziedzic)) +- Upload local fingerprint on `eas fingerprint:compare`. ([#2808](https://github.com/expo/eas-cli/pull/2808) by [@quinlanj](https://github.com/quinlanj)) ## [14.4.0](https://github.com/expo/eas-cli/releases/tag/v14.4.0) - 2025-01-09 diff --git a/packages/eas-cli/graphql.schema.json b/packages/eas-cli/graphql.schema.json index 10b70b4d43..2e9633f4d8 100644 --- a/packages/eas-cli/graphql.schema.json +++ b/packages/eas-cli/graphql.schema.json @@ -4295,33 +4295,6 @@ "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", @@ -4335,11 +4308,6 @@ "kind": "OBJECT", "name": "AccountUsageEASBuildMetadata", "ofType": null - }, - { - "kind": "OBJECT", - "name": "AccountUsageEASJobsMetadata", - "ofType": null } ] }, @@ -8918,6 +8886,83 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "fingerprintsPaginated", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "filter", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "FingerprintFilterInput", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "AppFingerprintsConnection", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "fullName", "description": null, @@ -11380,6 +11425,100 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "AppFingerprintEdge", + "description": null, + "fields": [ + { + "name": "cursor", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "node", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Fingerprint", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "AppFingerprintsConnection", + "description": null, + "fields": [ + { + "name": "edges", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "AppFingerprintEdge", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pageInfo", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "AppIcon", @@ -23106,6 +23245,45 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "CreateFingerprintInput", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "hash", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "source", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "FingerprintSourceInput", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "CreateGitHubAppInstallationInput", @@ -26980,6 +27158,24 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "WorkerCustomDomainEntity", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "WorkerDeploymentAliasEntity", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "WorkerEntity", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "WorkflowEntity", "description": null, @@ -28949,6 +29145,37 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "FingerprintFilterInput", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "hashes", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "FingerprintInfo", @@ -29039,6 +29266,66 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "FingerprintMutation", + "description": null, + "fields": [ + { + "name": "createOrGetExistingFingerprint", + "description": "Create or get an existing fingerprint for an App", + "args": [ + { + "name": "appId", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fingerprintData", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "CreateFingerprintInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Fingerprint", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "FingerprintSource", @@ -38875,6 +39162,26 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "requestId", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "WorkerDeploymentRequestID", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "responseType", "description": null, @@ -40208,6 +40515,22 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "fingerprint", + "description": "Mutations that modify App fingerprints", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "FingerprintMutation", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "githubApp", "description": "Mutations that utilize services facilitated by the GitHub App", @@ -55087,6 +55410,16 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "SCALAR", + "name": "WorkerDeploymentRequestID", + "description": "A Worker Deployment Request ID (also known as the CF Ray ID)", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "WorkerDeploymentRequestNode", @@ -55384,6 +55717,22 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "requestId", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "WorkerDeploymentRequestID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "requestTimestamp", "description": null, @@ -58665,6 +59014,22 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "triggerEventType", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "WorkflowRunTriggerEventType", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "updatedAt", "description": null, @@ -59036,6 +59401,29 @@ ], "possibleTypes": null }, + { + "kind": "ENUM", + "name": "WorkflowRunTriggerEventType", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "GITHUB", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MANUAL", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, { "kind": "OBJECT", "name": "WorkflowRunsConnection", diff --git a/packages/eas-cli/src/commands/fingerprint/compare.ts b/packages/eas-cli/src/commands/fingerprint/compare.ts index 714e29125a..00b556bf41 100644 --- a/packages/eas-cli/src/commands/fingerprint/compare.ts +++ b/packages/eas-cli/src/commands/fingerprint/compare.ts @@ -7,10 +7,12 @@ import { fetchBuildsAsync, formatBuild } from '../../commandUtils/builds'; import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient'; import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags'; import { AppPlatform, BuildStatus } from '../../graphql/generated'; +import { FingerprintMutation } from '../../graphql/mutations/FingerprintMutation'; import { BuildQuery } from '../../graphql/queries/BuildQuery'; import Log from '../../log'; import { ora } from '../../ora'; import { RequestedPlatform } from '../../platform'; +import { maybeUploadFingerprintAsync } from '../../project/maybeUploadFingerprintAsync'; import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils'; import { resolveWorkflowPerPlatformAsync } from '../../project/workflow'; import { selectAsync } from '../../prompts'; @@ -94,6 +96,20 @@ export default class FingerprintCompare extends EasCommand { Log.error('Project fingerprints can only be computed for projects with SDK 52 or higher'); return; } + + const uploadedFingerprint = await maybeUploadFingerprintAsync({ + hash: fingerprint.hash, + fingerprint: { + fingerprintSources: fingerprint.sources, + isDebugFingerprintSource: Log.isDebug, + }, + graphqlClient, + }); + await FingerprintMutation.createFingerprintAsync(graphqlClient, projectId, { + hash: uploadedFingerprint.hash, + source: uploadedFingerprint.fingerprintSource, + }); + if (fingerprint.hash === projectFingerprint.hash) { Log.log(`✅ Project fingerprint matches build`); return; diff --git a/packages/eas-cli/src/graphql/generated.ts b/packages/eas-cli/src/graphql/generated.ts index 1b7d092fd5..6990140b94 100644 --- a/packages/eas-cli/src/graphql/generated.ts +++ b/packages/eas-cli/src/graphql/generated.ts @@ -24,6 +24,7 @@ export type Scalars = { JSON: { input: any; output: any; } JSONObject: { input: any; output: any; } WorkerDeploymentIdentifier: { input: any; output: any; } + WorkerDeploymentRequestID: { input: any; output: any; } }; export type AcceptUserInvitationResult = { @@ -725,12 +726,7 @@ export type AccountUsageEasBuildMetadata = { waiverType?: Maybe<EasBuildWaiverType>; }; -export type AccountUsageEasJobsMetadata = { - __typename?: 'AccountUsageEASJobsMetadata'; - resourceClassDisplayName: Scalars['String']['output']; -}; - -export type AccountUsageMetadata = AccountUsageEasBuildMetadata | AccountUsageEasJobsMetadata; +export type AccountUsageMetadata = AccountUsageEasBuildMetadata; export type AccountUsageMetric = { __typename?: 'AccountUsageMetric'; @@ -1254,6 +1250,7 @@ export type App = Project & { environmentVariables: Array<EnvironmentVariable>; /** Environment variables for an app with decrypted secret values */ environmentVariablesIncludingSensitive: Array<EnvironmentVariableWithSecret>; + fingerprintsPaginated: AppFingerprintsConnection; fullName: Scalars['String']['output']; githubBuildTriggers: Array<GitHubBuildTrigger>; githubJobRunTriggers: Array<GitHubJobRunTrigger>; @@ -1484,6 +1481,16 @@ export type AppEnvironmentVariablesIncludingSensitiveArgs = { }; +/** Represents an Exponent App (or Experience in legacy terms) */ +export type AppFingerprintsPaginatedArgs = { + after?: InputMaybe<Scalars['String']['input']>; + before?: InputMaybe<Scalars['String']['input']>; + filter?: InputMaybe<FingerprintFilterInput>; + first?: InputMaybe<Scalars['Int']['input']>; + last?: InputMaybe<Scalars['Int']['input']>; +}; + + /** Represents an Exponent App (or Experience in legacy terms) */ export type AppIosAppCredentialsArgs = { filter?: InputMaybe<IosAppCredentialsFilter>; @@ -1737,6 +1744,18 @@ export type AppDevDomainNameMutationChangeDevDomainNameArgs = { name: Scalars['DevDomainName']['input']; }; +export type AppFingerprintEdge = { + __typename?: 'AppFingerprintEdge'; + cursor: Scalars['String']['output']; + node: Fingerprint; +}; + +export type AppFingerprintsConnection = { + __typename?: 'AppFingerprintsConnection'; + edges: Array<AppFingerprintEdge>; + pageInfo: PageInfo; +}; + export type AppIcon = { __typename?: 'AppIcon'; /** @deprecated No longer supported */ @@ -3390,6 +3409,11 @@ export type CreateEnvironmentVariableInput = { visibility: EnvironmentVariableVisibility; }; +export type CreateFingerprintInput = { + hash: Scalars['String']['input']; + source?: InputMaybe<FingerprintSourceInput>; +}; + export type CreateGitHubAppInstallationInput = { accountId: Scalars['ID']['input']; installationIdentifier: Scalars['Int']['input']; @@ -3962,6 +3986,9 @@ export enum EntityTypeName { IosAppCredentialsEntity = 'IosAppCredentialsEntity', UserInvitationEntity = 'UserInvitationEntity', UserPermissionEntity = 'UserPermissionEntity', + WorkerCustomDomainEntity = 'WorkerCustomDomainEntity', + WorkerDeploymentAliasEntity = 'WorkerDeploymentAliasEntity', + WorkerEntity = 'WorkerEntity', WorkflowEntity = 'WorkflowEntity', WorkflowRevisionEntity = 'WorkflowRevisionEntity' } @@ -4232,6 +4259,10 @@ export type Fingerprint = { updatedAt: Scalars['DateTime']['output']; }; +export type FingerprintFilterInput = { + hashes?: InputMaybe<Array<Scalars['String']['input']>>; +}; + export type FingerprintInfo = { fingerprintHash: Scalars['String']['input']; fingerprintSource: FingerprintSourceInput; @@ -4243,6 +4274,18 @@ export type FingerprintInfoGroup = { web?: InputMaybe<FingerprintInfo>; }; +export type FingerprintMutation = { + __typename?: 'FingerprintMutation'; + /** Create or get an existing fingerprint for an App */ + createOrGetExistingFingerprint: Fingerprint; +}; + + +export type FingerprintMutationCreateOrGetExistingFingerprintArgs = { + appId: Scalars['ID']['input']; + fingerprintData: CreateFingerprintInput; +}; + export type FingerprintSource = { __typename?: 'FingerprintSource'; bucketKey: Scalars['String']['output']; @@ -5591,6 +5634,7 @@ export type RequestsFilters = { method?: InputMaybe<Array<RequestMethod>>; os?: InputMaybe<Array<UserAgentOs>>; pathname?: InputMaybe<Scalars['String']['input']>; + requestId?: InputMaybe<Array<Scalars['WorkerDeploymentRequestID']['input']>>; responseType?: InputMaybe<Array<ResponseType>>; status?: InputMaybe<Array<Scalars['Int']['input']>>; statusType?: InputMaybe<Array<ResponseStatusType>>; @@ -5780,6 +5824,8 @@ export type RootMutation = { environmentSecret: EnvironmentSecretMutation; /** Mutations that create and delete EnvironmentVariables */ environmentVariable: EnvironmentVariableMutation; + /** Mutations that modify App fingerprints */ + fingerprint: FingerprintMutation; /** Mutations that utilize services facilitated by the GitHub App */ githubApp: GitHubAppMutation; /** Mutations for GitHub App installations */ @@ -7890,6 +7936,7 @@ export type WorkerDeploymentRequestNode = { os?: Maybe<UserAgentOs>; pathname: Scalars['String']['output']; region?: Maybe<Scalars['String']['output']>; + requestId: Scalars['WorkerDeploymentRequestID']['output']; requestTimestamp: Scalars['DateTime']['output']; responseType: ResponseType; scriptName: Scalars['String']['output']; @@ -8266,6 +8313,7 @@ export type WorkflowRun = ActivityTimelineProjectActivity & { pullRequestNumber?: Maybe<Scalars['Int']['output']>; requestedGitRef?: Maybe<Scalars['String']['output']>; status: WorkflowRunStatus; + triggerEventType: WorkflowRunTriggerEventType; updatedAt: Scalars['DateTime']['output']; workflow: Workflow; workflowRevision?: Maybe<WorkflowRevision>; @@ -8325,6 +8373,11 @@ export enum WorkflowRunStatus { Success = 'SUCCESS' } +export enum WorkflowRunTriggerEventType { + Github = 'GITHUB', + Manual = 'MANUAL' +} + export type WorkflowRunsConnection = { __typename?: 'WorkflowRunsConnection'; edges: Array<WorkflowRunEdge>; @@ -8987,6 +9040,14 @@ export type CreateBulkEnvironmentVariablesForAppMutationVariables = Exact<{ export type CreateBulkEnvironmentVariablesForAppMutation = { __typename?: 'RootMutation', environmentVariable: { __typename?: 'EnvironmentVariableMutation', createBulkEnvironmentVariablesForApp: Array<{ __typename?: 'EnvironmentVariable', id: string }> } }; +export type CreateFingeprintMutationVariables = Exact<{ + fingerprintData: CreateFingerprintInput; + appId: Scalars['ID']['input']; +}>; + + +export type CreateFingeprintMutation = { __typename?: 'RootMutation', fingerprint: { __typename?: 'FingerprintMutation', createOrGetExistingFingerprint: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null } } }; + export type CreateKeystoreGenerationUrlMutationVariables = Exact<{ [key: string]: never; }>; diff --git a/packages/eas-cli/src/graphql/mutations/FingerprintMutation.ts b/packages/eas-cli/src/graphql/mutations/FingerprintMutation.ts new file mode 100644 index 0000000000..87c8023b27 --- /dev/null +++ b/packages/eas-cli/src/graphql/mutations/FingerprintMutation.ts @@ -0,0 +1,39 @@ +import { FingerprintSource } from '@expo/eas-build-job'; +import { print } from 'graphql'; +import gql from 'graphql-tag'; + +import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient'; +import { withErrorHandlingAsync } from '../client'; +import { CreateFingeprintMutation, FingerprintFragment } from '../generated'; +import { FingerprintFragmentNode } from '../types/Fingerprint'; + +export const FingerprintMutation = { + async createFingerprintAsync( + graphqlClient: ExpoGraphqlClient, + appId: string, + fingerprintData: { hash: string; source?: FingerprintSource } + ): Promise<FingerprintFragment> { + const data = await withErrorHandlingAsync( + graphqlClient + .mutation<CreateFingeprintMutation>( + gql` + mutation CreateFingeprintMutation( + $fingerprintData: CreateFingerprintInput! + $appId: ID! + ) { + fingerprint { + createOrGetExistingFingerprint(fingerprintData: $fingerprintData, appId: $appId) { + id + ...FingerprintFragment + } + } + } + ${print(FingerprintFragmentNode)} + `, + { appId, fingerprintData } + ) + .toPromise() + ); + return data.fingerprint.createOrGetExistingFingerprint; + }, +};