decap-cms-backend-github 2.15.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,709 @@
1
+ import { ApolloClient } from 'apollo-client';
2
+ import {
3
+ InMemoryCache,
4
+ defaultDataIdFromObject,
5
+ IntrospectionFragmentMatcher,
6
+ } from 'apollo-cache-inmemory';
7
+ import { createHttpLink } from 'apollo-link-http';
8
+ import { setContext } from 'apollo-link-context';
9
+ import {
10
+ APIError,
11
+ readFile,
12
+ localForage,
13
+ DEFAULT_PR_BODY,
14
+ branchFromContentKey,
15
+ CMS_BRANCH_PREFIX,
16
+ throwOnConflictingBranches,
17
+ } from 'decap-cms-lib-util';
18
+ import { trim, trimStart } from 'lodash';
19
+
20
+ import introspectionQueryResultData from './fragmentTypes';
21
+ import API, { API_NAME, PullRequestState, MOCK_PULL_REQUEST } from './API';
22
+ import * as queries from './queries';
23
+ import * as mutations from './mutations';
24
+
25
+ import type { Config, BlobArgs } from './API';
26
+ import type { NormalizedCacheObject } from 'apollo-cache-inmemory';
27
+ import type { QueryOptions, MutationOptions, OperationVariables } from 'apollo-client';
28
+ import type { GraphQLError } from 'graphql';
29
+ import type { Octokit } from '@octokit/rest';
30
+
31
+ const NO_CACHE = 'no-cache';
32
+ const CACHE_FIRST = 'cache-first';
33
+
34
+ const fragmentMatcher = new IntrospectionFragmentMatcher({
35
+ introspectionQueryResultData,
36
+ });
37
+
38
+ interface TreeEntry {
39
+ object?: {
40
+ entries: TreeEntry[];
41
+ };
42
+ type: 'blob' | 'tree';
43
+ name: string;
44
+ sha: string;
45
+ blob?: {
46
+ size: number;
47
+ };
48
+ }
49
+
50
+ interface TreeFile {
51
+ path: string;
52
+ id: string;
53
+ size: number;
54
+ type: string;
55
+ name: string;
56
+ }
57
+
58
+ type GraphQLPullRequest = {
59
+ id: string;
60
+ baseRefName: string;
61
+ baseRefOid: string;
62
+ body: string;
63
+ headRefName: string;
64
+ headRefOid: string;
65
+ number: number;
66
+ state: string;
67
+ title: string;
68
+ mergedAt: string | null;
69
+ updatedAt: string | null;
70
+ labels: { nodes: { name: string }[] };
71
+ repository: {
72
+ id: string;
73
+ isFork: boolean;
74
+ };
75
+ user: GraphQLPullsListResponseItemUser;
76
+ };
77
+
78
+ type GraphQLPullsListResponseItemUser = {
79
+ avatar_url: string;
80
+ login: string;
81
+ url: string;
82
+ name: string;
83
+ };
84
+
85
+ function transformPullRequest(pr: GraphQLPullRequest) {
86
+ return {
87
+ ...pr,
88
+ labels: pr.labels.nodes,
89
+ head: { ref: pr.headRefName, sha: pr.headRefOid, repo: { fork: pr.repository.isFork } },
90
+ base: { ref: pr.baseRefName, sha: pr.baseRefOid },
91
+ };
92
+ }
93
+
94
+ type Error = GraphQLError & { type: string };
95
+
96
+ export default class GraphQLAPI extends API {
97
+ client: ApolloClient<NormalizedCacheObject>;
98
+
99
+ constructor(config: Config) {
100
+ super(config);
101
+
102
+ this.client = this.getApolloClient();
103
+ }
104
+
105
+ getApolloClient() {
106
+ const authLink = setContext((_, { headers }) => {
107
+ return {
108
+ headers: {
109
+ 'Content-Type': 'application/json; charset=utf-8',
110
+ ...headers,
111
+ authorization: this.token ? `token ${this.token}` : '',
112
+ },
113
+ };
114
+ });
115
+ const httpLink = createHttpLink({ uri: `${this.apiRoot}/graphql` });
116
+ return new ApolloClient({
117
+ link: authLink.concat(httpLink),
118
+ cache: new InMemoryCache({ fragmentMatcher }),
119
+ defaultOptions: {
120
+ watchQuery: {
121
+ fetchPolicy: NO_CACHE,
122
+ errorPolicy: 'ignore',
123
+ },
124
+ query: {
125
+ fetchPolicy: NO_CACHE,
126
+ errorPolicy: 'all',
127
+ },
128
+ },
129
+ });
130
+ }
131
+
132
+ reset() {
133
+ return this.client.resetStore();
134
+ }
135
+
136
+ async getRepository(owner: string, name: string) {
137
+ const { data } = await this.query({
138
+ query: queries.repository,
139
+ variables: { owner, name },
140
+ fetchPolicy: CACHE_FIRST, // repository id doesn't change
141
+ });
142
+ return data.repository;
143
+ }
144
+
145
+ query(options: QueryOptions<OperationVariables>) {
146
+ return this.client.query(options).catch(error => {
147
+ throw new APIError(error.message, 500, 'GitHub');
148
+ });
149
+ }
150
+
151
+ async mutate(options: MutationOptions<OperationVariables>) {
152
+ try {
153
+ const result = await this.client.mutate(options);
154
+ return result;
155
+ } catch (error) {
156
+ const errors = error.graphQLErrors;
157
+ if (Array.isArray(errors) && errors.some(e => e.message === 'Ref cannot be created.')) {
158
+ const refName = options?.variables?.createRefInput?.name || '';
159
+ const branchName = trimStart(refName, 'refs/heads/');
160
+ if (branchName) {
161
+ await throwOnConflictingBranches(branchName, name => this.getBranch(name), API_NAME);
162
+ }
163
+ } else if (
164
+ Array.isArray(errors) &&
165
+ errors.some(e =>
166
+ new RegExp(
167
+ `A ref named "refs/heads/${CMS_BRANCH_PREFIX}/.+?" already exists in the repository.`,
168
+ ).test(e.message),
169
+ )
170
+ ) {
171
+ const refName = options?.variables?.createRefInput?.name || '';
172
+ const sha = options?.variables?.createRefInput?.oid || '';
173
+ const branchName = trimStart(refName, 'refs/heads/');
174
+ if (branchName && branchName.startsWith(`${CMS_BRANCH_PREFIX}/`) && sha) {
175
+ try {
176
+ // this can happen if the branch wasn't deleted when the PR was merged
177
+ // we backup the existing branch just in case an re-run the mutation
178
+ await this.backupBranch(branchName);
179
+ await this.deleteBranch(branchName);
180
+ const result = await this.client.mutate(options);
181
+ return result;
182
+ } catch (e) {
183
+ console.log(e);
184
+ }
185
+ }
186
+ }
187
+ throw new APIError(error.message, 500, 'GitHub');
188
+ }
189
+ }
190
+
191
+ async hasWriteAccess() {
192
+ const { repoOwner: owner, repoName: name } = this;
193
+ try {
194
+ const { data } = await this.query({
195
+ query: queries.repoPermission,
196
+ variables: { owner, name },
197
+ fetchPolicy: CACHE_FIRST, // we can assume permission doesn't change often
198
+ });
199
+ // https://developer.github.com/v4/enum/repositorypermission/
200
+ const { viewerPermission } = data.repository;
201
+ return ['ADMIN', 'MAINTAIN', 'WRITE'].includes(viewerPermission);
202
+ } catch (error) {
203
+ console.error('Problem fetching repo data from GitHub');
204
+ throw error;
205
+ }
206
+ }
207
+
208
+ async user() {
209
+ const { data } = await this.query({
210
+ query: queries.user,
211
+ fetchPolicy: CACHE_FIRST, // we can assume user details don't change often
212
+ });
213
+ return data.viewer;
214
+ }
215
+
216
+ async retrieveBlobObject(owner: string, name: string, expression: string, options = {}) {
217
+ const { data } = await this.query({
218
+ query: queries.blob,
219
+ variables: { owner, name, expression },
220
+ ...options,
221
+ });
222
+ // https://developer.github.com/v4/object/blob/
223
+ if (data.repository.object) {
224
+ const { is_binary: isBinary, text } = data.repository.object;
225
+ return { isNull: false, isBinary, text };
226
+ } else {
227
+ return { isNull: true };
228
+ }
229
+ }
230
+
231
+ getOwnerAndNameFromRepoUrl(repoURL: string) {
232
+ let { repoOwner: owner, repoName: name } = this;
233
+
234
+ if (repoURL === this.originRepoURL) {
235
+ ({ originRepoOwner: owner, originRepoName: name } = this);
236
+ }
237
+
238
+ return { owner, name };
239
+ }
240
+
241
+ async readFile(
242
+ path: string,
243
+ sha?: string | null,
244
+ {
245
+ branch = this.branch,
246
+ repoURL = this.repoURL,
247
+ parseText = true,
248
+ }: {
249
+ branch?: string;
250
+ repoURL?: string;
251
+ parseText?: boolean;
252
+ } = {},
253
+ ) {
254
+ if (!sha) {
255
+ sha = await this.getFileSha(path, { repoURL, branch });
256
+ }
257
+ const fetchContent = () => this.fetchBlobContent({ sha: sha as string, repoURL, parseText });
258
+ const content = await readFile(sha, fetchContent, localForage, parseText);
259
+ return content;
260
+ }
261
+
262
+ async fetchBlobContent({ sha, repoURL, parseText }: BlobArgs) {
263
+ if (!parseText) {
264
+ return super.fetchBlobContent({ sha, repoURL, parseText });
265
+ }
266
+ const { owner, name } = this.getOwnerAndNameFromRepoUrl(repoURL);
267
+ const { isNull, isBinary, text } = await this.retrieveBlobObject(
268
+ owner,
269
+ name,
270
+ sha,
271
+ { fetchPolicy: CACHE_FIRST }, // blob sha is derived from file content
272
+ );
273
+
274
+ if (isNull) {
275
+ throw new APIError('Not Found', 404, 'GitHub');
276
+ } else if (!isBinary) {
277
+ return text;
278
+ } else {
279
+ return super.fetchBlobContent({ sha, repoURL, parseText });
280
+ }
281
+ }
282
+
283
+ async getPullRequestAuthor(pullRequest: Octokit.PullsListResponseItem) {
284
+ const user = pullRequest.user as unknown as GraphQLPullsListResponseItemUser;
285
+ return user?.name || user?.login;
286
+ }
287
+
288
+ async getPullRequests(
289
+ head: string | undefined,
290
+ state: PullRequestState,
291
+ predicate: (pr: Octokit.PullsListResponseItem) => boolean,
292
+ ) {
293
+ const { originRepoOwner: owner, originRepoName: name } = this;
294
+ let states;
295
+ if (state === PullRequestState.Open) {
296
+ states = ['OPEN'];
297
+ } else if (state === PullRequestState.Closed) {
298
+ states = ['CLOSED', 'MERGED'];
299
+ } else {
300
+ states = ['OPEN', 'CLOSED', 'MERGED'];
301
+ }
302
+ const { data } = await this.query({
303
+ query: queries.pullRequests,
304
+ variables: {
305
+ owner,
306
+ name,
307
+ ...(head ? { head } : {}),
308
+ states,
309
+ },
310
+ });
311
+ const {
312
+ pullRequests,
313
+ }: {
314
+ pullRequests: {
315
+ nodes: GraphQLPullRequest[];
316
+ };
317
+ } = data.repository;
318
+
319
+ const mapped = pullRequests.nodes.map(transformPullRequest);
320
+
321
+ return (mapped as unknown as Octokit.PullsListResponseItem[]).filter(
322
+ pr => pr.head.ref.startsWith(`${CMS_BRANCH_PREFIX}/`) && predicate(pr),
323
+ );
324
+ }
325
+
326
+ async getOpenAuthoringBranches() {
327
+ const { repoOwner: owner, repoName: name } = this;
328
+ const { data } = await this.query({
329
+ query: queries.openAuthoringBranches,
330
+ variables: {
331
+ owner,
332
+ name,
333
+ refPrefix: `refs/heads/cms/${this.repo}/`,
334
+ },
335
+ });
336
+
337
+ return data.repository.refs.nodes.map(({ name, prefix }: { name: string; prefix: string }) => ({
338
+ ref: `${prefix}${name}`,
339
+ }));
340
+ }
341
+
342
+ async getStatuses(collectionName: string, slug: string) {
343
+ const contentKey = this.generateContentKey(collectionName, slug);
344
+ const branch = branchFromContentKey(contentKey);
345
+ const pullRequest = await this.getBranchPullRequest(branch);
346
+ const sha = pullRequest.head.sha;
347
+ const { originRepoOwner: owner, originRepoName: name } = this;
348
+ const { data } = await this.query({ query: queries.statues, variables: { owner, name, sha } });
349
+ if (data.repository.object) {
350
+ const { status } = data.repository.object;
351
+ const { contexts } = status || { contexts: [] };
352
+ return contexts;
353
+ } else {
354
+ return [];
355
+ }
356
+ }
357
+
358
+ getAllFiles(entries: TreeEntry[], path: string) {
359
+ const allFiles: TreeFile[] = entries.reduce((acc, item) => {
360
+ if (item.type === 'tree') {
361
+ const entries = item.object?.entries || [];
362
+ return [...acc, ...this.getAllFiles(entries, `${path}/${item.name}`)];
363
+ } else if (item.type === 'blob') {
364
+ return [
365
+ ...acc,
366
+ {
367
+ name: item.name,
368
+ type: item.type,
369
+ id: item.sha,
370
+ path: `${path}/${item.name}`,
371
+ size: item.blob ? item.blob.size : 0,
372
+ },
373
+ ];
374
+ }
375
+
376
+ return acc;
377
+ }, [] as TreeFile[]);
378
+ return allFiles;
379
+ }
380
+
381
+ async listFiles(path: string, { repoURL = this.repoURL, branch = this.branch, depth = 1 } = {}) {
382
+ const { owner, name } = this.getOwnerAndNameFromRepoUrl(repoURL);
383
+ const folder = trim(path, '/');
384
+ const { data } = await this.query({
385
+ query: queries.files(depth),
386
+ variables: { owner, name, expression: `${branch}:${folder}` },
387
+ });
388
+
389
+ if (data.repository.object) {
390
+ const allFiles = this.getAllFiles(data.repository.object.entries, folder);
391
+ return allFiles;
392
+ } else {
393
+ return [];
394
+ }
395
+ }
396
+
397
+ getBranchQualifiedName(branch: string) {
398
+ return `refs/heads/${branch}`;
399
+ }
400
+
401
+ getBranchQuery(branch: string, owner: string, name: string) {
402
+ return {
403
+ query: queries.branch,
404
+ variables: {
405
+ owner,
406
+ name,
407
+ qualifiedName: this.getBranchQualifiedName(branch),
408
+ },
409
+ };
410
+ }
411
+
412
+ async getDefaultBranch() {
413
+ const { data } = await this.query({
414
+ ...this.getBranchQuery(this.branch, this.originRepoOwner, this.originRepoName),
415
+ });
416
+ return data.repository.branch;
417
+ }
418
+
419
+ async getBranch(branch: string) {
420
+ const { data } = await this.query({
421
+ ...this.getBranchQuery(branch, this.repoOwner, this.repoName),
422
+ fetchPolicy: CACHE_FIRST,
423
+ });
424
+ if (!data.repository.branch) {
425
+ throw new APIError('Branch not found', 404, API_NAME);
426
+ }
427
+ return data.repository.branch;
428
+ }
429
+
430
+ async patchRef(type: string, name: string, sha: string, opts: { force?: boolean } = {}) {
431
+ if (type !== 'heads') {
432
+ return super.patchRef(type, name, sha, opts);
433
+ }
434
+
435
+ const force = opts.force || false;
436
+
437
+ const branch = await this.getBranch(name);
438
+ const { data } = await this.mutate({
439
+ mutation: mutations.updateBranch,
440
+ variables: {
441
+ input: { oid: sha, refId: branch.id, force },
442
+ },
443
+ });
444
+ return data!.updateRef.branch;
445
+ }
446
+
447
+ async deleteBranch(branchName: string) {
448
+ const branch = await this.getBranch(branchName);
449
+ const { data } = await this.mutate({
450
+ mutation: mutations.deleteBranch,
451
+ variables: {
452
+ deleteRefInput: { refId: branch.id },
453
+ },
454
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
455
+ update: (store: any) => store.data.delete(defaultDataIdFromObject(branch)),
456
+ });
457
+
458
+ return data!.deleteRef;
459
+ }
460
+
461
+ getPullRequestQuery(number: number) {
462
+ const { originRepoOwner: owner, originRepoName: name } = this;
463
+
464
+ return {
465
+ query: queries.pullRequest,
466
+ variables: { owner, name, number },
467
+ };
468
+ }
469
+
470
+ async getPullRequest(number: number) {
471
+ const { data } = await this.query({
472
+ ...this.getPullRequestQuery(number),
473
+ fetchPolicy: CACHE_FIRST,
474
+ });
475
+
476
+ // https://developer.github.com/v4/enum/pullrequeststate/
477
+ // GraphQL state: [CLOSED, MERGED, OPEN]
478
+ // REST API state: [closed, open]
479
+ const state =
480
+ data.repository.pullRequest.state === 'OPEN'
481
+ ? PullRequestState.Open
482
+ : PullRequestState.Closed;
483
+ return {
484
+ ...data.repository.pullRequest,
485
+ state,
486
+ };
487
+ }
488
+
489
+ getPullRequestAndBranchQuery(branch: string, number: number) {
490
+ const { repoOwner: owner, repoName: name } = this;
491
+ const { originRepoOwner, originRepoName } = this;
492
+ return {
493
+ query: queries.pullRequestAndBranch,
494
+ variables: {
495
+ owner,
496
+ name,
497
+ originRepoOwner,
498
+ originRepoName,
499
+ number,
500
+ qualifiedName: this.getBranchQualifiedName(branch),
501
+ },
502
+ };
503
+ }
504
+
505
+ async getPullRequestAndBranch(branch: string, number: number) {
506
+ const { data } = await this.query({
507
+ ...this.getPullRequestAndBranchQuery(branch, number),
508
+ fetchPolicy: CACHE_FIRST,
509
+ });
510
+
511
+ const { repository, origin } = data;
512
+ return { branch: repository.branch, pullRequest: origin.pullRequest };
513
+ }
514
+
515
+ async openPR(number: number) {
516
+ const pullRequest = await this.getPullRequest(number);
517
+
518
+ const { data } = await this.mutate({
519
+ mutation: mutations.reopenPullRequest,
520
+ variables: {
521
+ reopenPullRequestInput: { pullRequestId: pullRequest.id },
522
+ },
523
+ update: (store, { data: mutationResult }) => {
524
+ const { pullRequest } = mutationResult!.reopenPullRequest;
525
+ const pullRequestData = { repository: { ...pullRequest.repository, pullRequest } };
526
+
527
+ store.writeQuery({
528
+ ...this.getPullRequestQuery(pullRequest.number),
529
+ data: pullRequestData,
530
+ });
531
+ },
532
+ });
533
+
534
+ return data!.reopenPullRequest;
535
+ }
536
+
537
+ async closePR(number: number) {
538
+ const pullRequest = await this.getPullRequest(number);
539
+
540
+ const { data } = await this.mutate({
541
+ mutation: mutations.closePullRequest,
542
+ variables: {
543
+ closePullRequestInput: { pullRequestId: pullRequest.id },
544
+ },
545
+ update: (store, { data: mutationResult }) => {
546
+ const { pullRequest } = mutationResult!.closePullRequest;
547
+ const pullRequestData = { repository: { ...pullRequest.repository, pullRequest } };
548
+
549
+ store.writeQuery({
550
+ ...this.getPullRequestQuery(pullRequest.number),
551
+ data: pullRequestData,
552
+ });
553
+ },
554
+ });
555
+
556
+ return data!.closePullRequest;
557
+ }
558
+
559
+ async deleteUnpublishedEntry(collectionName: string, slug: string) {
560
+ try {
561
+ const contentKey = this.generateContentKey(collectionName, slug);
562
+ const branchName = branchFromContentKey(contentKey);
563
+ const pr = await this.getBranchPullRequest(branchName);
564
+ if (pr.number !== MOCK_PULL_REQUEST) {
565
+ const { branch, pullRequest } = await this.getPullRequestAndBranch(branchName, pr.number);
566
+
567
+ const { data } = await this.mutate({
568
+ mutation: mutations.closePullRequestAndDeleteBranch,
569
+ variables: {
570
+ deleteRefInput: { refId: branch.id },
571
+ closePullRequestInput: { pullRequestId: pullRequest.id },
572
+ },
573
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
574
+ update: (store: any) => {
575
+ store.data.delete(defaultDataIdFromObject(branch));
576
+ store.data.delete(defaultDataIdFromObject(pullRequest));
577
+ },
578
+ });
579
+
580
+ return data!.closePullRequest;
581
+ } else {
582
+ return await this.deleteBranch(branchName);
583
+ }
584
+ } catch (e) {
585
+ const { graphQLErrors } = e;
586
+ if (graphQLErrors && graphQLErrors.length > 0) {
587
+ const branchNotFound = graphQLErrors.some((e: Error) => e.type === 'NOT_FOUND');
588
+ if (branchNotFound) {
589
+ return;
590
+ }
591
+ }
592
+ throw e;
593
+ }
594
+ }
595
+
596
+ async createPR(title: string, head: string) {
597
+ const [repository, headReference] = await Promise.all([
598
+ this.getRepository(this.originRepoOwner, this.originRepoName),
599
+ this.useOpenAuthoring ? `${(await this.user()).login}:${head}` : head,
600
+ ]);
601
+ const { data } = await this.mutate({
602
+ mutation: mutations.createPullRequest,
603
+ variables: {
604
+ createPullRequestInput: {
605
+ baseRefName: this.branch,
606
+ body: DEFAULT_PR_BODY,
607
+ title,
608
+ headRefName: headReference,
609
+ repositoryId: repository.id,
610
+ },
611
+ },
612
+ update: (store, { data: mutationResult }) => {
613
+ const { pullRequest } = mutationResult!.createPullRequest;
614
+ const pullRequestData = { repository: { ...pullRequest.repository, pullRequest } };
615
+
616
+ store.writeQuery({
617
+ ...this.getPullRequestQuery(pullRequest.number),
618
+ data: pullRequestData,
619
+ });
620
+ },
621
+ });
622
+ const { pullRequest } = data!.createPullRequest;
623
+ return { ...pullRequest, head: { sha: pullRequest.headRefOid } };
624
+ }
625
+
626
+ async createBranch(branchName: string, sha: string) {
627
+ const owner = this.repoOwner;
628
+ const name = this.repoName;
629
+ const repository = await this.getRepository(owner, name);
630
+ const { data } = await this.mutate({
631
+ mutation: mutations.createBranch,
632
+ variables: {
633
+ createRefInput: {
634
+ name: this.getBranchQualifiedName(branchName),
635
+ oid: sha,
636
+ repositoryId: repository.id,
637
+ },
638
+ },
639
+ update: (store, { data: mutationResult }) => {
640
+ const { branch } = mutationResult!.createRef;
641
+ const branchData = { repository: { ...branch.repository, branch } };
642
+
643
+ store.writeQuery({
644
+ ...this.getBranchQuery(branchName, owner, name),
645
+ data: branchData,
646
+ });
647
+ },
648
+ });
649
+ const { branch } = data!.createRef;
650
+ return { ...branch, ref: `${branch.prefix}${branch.name}` };
651
+ }
652
+
653
+ async createBranchAndPullRequest(branchName: string, sha: string, title: string) {
654
+ const owner = this.originRepoOwner;
655
+ const name = this.originRepoName;
656
+ const repository = await this.getRepository(owner, name);
657
+ const { data } = await this.mutate({
658
+ mutation: mutations.createBranchAndPullRequest,
659
+ variables: {
660
+ createRefInput: {
661
+ name: this.getBranchQualifiedName(branchName),
662
+ oid: sha,
663
+ repositoryId: repository.id,
664
+ },
665
+ createPullRequestInput: {
666
+ baseRefName: this.branch,
667
+ body: DEFAULT_PR_BODY,
668
+ title,
669
+ headRefName: branchName,
670
+ repositoryId: repository.id,
671
+ },
672
+ },
673
+ update: (store, { data: mutationResult }) => {
674
+ const { branch } = mutationResult!.createRef;
675
+ const { pullRequest } = mutationResult!.createPullRequest;
676
+ const branchData = { repository: { ...branch.repository, branch } };
677
+ const pullRequestData = {
678
+ repository: { ...pullRequest.repository, branch },
679
+ origin: { ...pullRequest.repository, pullRequest },
680
+ };
681
+
682
+ store.writeQuery({
683
+ ...this.getBranchQuery(branchName, owner, name),
684
+ data: branchData,
685
+ });
686
+
687
+ store.writeQuery({
688
+ ...this.getPullRequestAndBranchQuery(branchName, pullRequest.number),
689
+ data: pullRequestData,
690
+ });
691
+ },
692
+ });
693
+ const { pullRequest } = data!.createPullRequest;
694
+ return transformPullRequest(pullRequest) as unknown as Octokit.PullsCreateResponse;
695
+ }
696
+
697
+ async getFileSha(path: string, { repoURL = this.repoURL, branch = this.branch } = {}) {
698
+ const { owner, name } = this.getOwnerAndNameFromRepoUrl(repoURL);
699
+ const { data } = await this.query({
700
+ query: queries.fileSha,
701
+ variables: { owner, name, expression: `${branch}:${path}` },
702
+ });
703
+
704
+ if (data.repository.file) {
705
+ return data.repository.file.sha;
706
+ }
707
+ throw new APIError('Not Found', 404, API_NAME);
708
+ }
709
+ }