github-issue-tower-defence-management 1.70.0 → 1.71.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +12 -0
- package/bin/adapter/entry-points/cli/index.js +48 -0
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js +88 -13
- package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js.map +1 -1
- package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js +26 -0
- package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js.map +1 -0
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.test.ts +189 -0
- package/src/adapter/entry-points/cli/index.ts +101 -0
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +184 -0
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.ts +118 -55
- package/src/domain/usecases/CheckIssueReviewReadinessUseCase.test.ts +196 -0
- package/src/domain/usecases/CheckIssueReviewReadinessUseCase.ts +45 -0
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +69 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts.map +1 -1
- package/types/domain/usecases/CheckIssueReviewReadinessUseCase.d.ts +21 -0
- package/types/domain/usecases/CheckIssueReviewReadinessUseCase.d.ts.map +1 -0
|
@@ -19,6 +19,7 @@ import { StartPreparationUseCase } from '../../../domain/usecases/StartPreparati
|
|
|
19
19
|
import { writeRotationOrderFile } from '../handlers/rotationOrderFileWriter';
|
|
20
20
|
import { ProxyClaudeTokenUsageRepository } from '../../repositories/ProxyClaudeTokenUsageRepository';
|
|
21
21
|
import { NotifyFinishedIssuePreparationUseCase } from '../../../domain/usecases/NotifyFinishedIssuePreparationUseCase';
|
|
22
|
+
import { CheckIssueReviewReadinessUseCase } from '../../../domain/usecases/CheckIssueReviewReadinessUseCase';
|
|
22
23
|
import { LocalStorageRepository } from '../../repositories/LocalStorageRepository';
|
|
23
24
|
import { GraphqlProjectRepository } from '../../repositories/GraphqlProjectRepository';
|
|
24
25
|
import { ApiV3IssueRepository } from '../../repositories/issue/ApiV3IssueRepository';
|
|
@@ -53,6 +54,12 @@ type NotifyFinishedOptions = {
|
|
|
53
54
|
configFilePath: string;
|
|
54
55
|
};
|
|
55
56
|
|
|
57
|
+
type CheckIssueReviewReadinessOptions = {
|
|
58
|
+
issueUrl: string;
|
|
59
|
+
projectUrl?: string;
|
|
60
|
+
configFilePath: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
56
63
|
const buildGithubRepositoryParams = (
|
|
57
64
|
localStorageRepository: LocalStorageRepository,
|
|
58
65
|
token: string,
|
|
@@ -444,6 +451,100 @@ program
|
|
|
444
451
|
});
|
|
445
452
|
});
|
|
446
453
|
|
|
454
|
+
program
|
|
455
|
+
.command('checkIssueReviewReadiness')
|
|
456
|
+
.description(
|
|
457
|
+
'Check whether an issue is in a review-ready state without mutating any field or posting any comment',
|
|
458
|
+
)
|
|
459
|
+
.requiredOption(
|
|
460
|
+
'--configFilePath <path>',
|
|
461
|
+
'Path to config file for tower defence management',
|
|
462
|
+
)
|
|
463
|
+
.requiredOption('--issueUrl <url>', 'GitHub issue URL')
|
|
464
|
+
.option('--projectUrl <url>', 'GitHub project URL')
|
|
465
|
+
.action(async (options: CheckIssueReviewReadinessOptions) => {
|
|
466
|
+
const token = process.env.GH_TOKEN;
|
|
467
|
+
if (!token) {
|
|
468
|
+
console.error('GH_TOKEN environment variable is required');
|
|
469
|
+
process.exit(1);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const configFileValues = loadConfigFile(options.configFilePath);
|
|
473
|
+
|
|
474
|
+
const cliOverrides: ConfigFile = {
|
|
475
|
+
projectUrl: options.projectUrl,
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
const tempProjectUrl =
|
|
479
|
+
cliOverrides.projectUrl ?? configFileValues.projectUrl;
|
|
480
|
+
|
|
481
|
+
let readmeOverrides: ConfigFile = {};
|
|
482
|
+
if (tempProjectUrl) {
|
|
483
|
+
const readme = await fetchProjectReadme(tempProjectUrl, token);
|
|
484
|
+
if (readme) {
|
|
485
|
+
readmeOverrides = parseProjectReadmeConfig(readme, tempProjectUrl);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const config = mergeConfigs(
|
|
490
|
+
configFileValues,
|
|
491
|
+
cliOverrides,
|
|
492
|
+
readmeOverrides,
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
const projectUrl = config.projectUrl;
|
|
496
|
+
|
|
497
|
+
if (!projectUrl) {
|
|
498
|
+
console.error(
|
|
499
|
+
'projectUrl is required. Provide via --projectUrl, config file, or project README.',
|
|
500
|
+
);
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const projectName = config.projectName ?? 'default';
|
|
505
|
+
const localStorageRepository = new LocalStorageRepository();
|
|
506
|
+
const cachePath = `./tmp/cache/${projectName}`;
|
|
507
|
+
const localStorageCacheRepository = new LocalStorageCacheRepository(
|
|
508
|
+
localStorageRepository,
|
|
509
|
+
cachePath,
|
|
510
|
+
);
|
|
511
|
+
const githubRepositoryParams = buildGithubRepositoryParams(
|
|
512
|
+
localStorageRepository,
|
|
513
|
+
token,
|
|
514
|
+
);
|
|
515
|
+
const projectRepository = new GraphqlProjectRepository(
|
|
516
|
+
...githubRepositoryParams,
|
|
517
|
+
);
|
|
518
|
+
const apiV3IssueRepository = new ApiV3IssueRepository(
|
|
519
|
+
...githubRepositoryParams,
|
|
520
|
+
);
|
|
521
|
+
const restIssueRepository = new RestIssueRepository(
|
|
522
|
+
...githubRepositoryParams,
|
|
523
|
+
);
|
|
524
|
+
const graphqlProjectItemRepository = new GraphqlProjectItemRepository(
|
|
525
|
+
...githubRepositoryParams,
|
|
526
|
+
);
|
|
527
|
+
const issueRepository = new ApiV3CheerioRestIssueRepository(
|
|
528
|
+
apiV3IssueRepository,
|
|
529
|
+
restIssueRepository,
|
|
530
|
+
graphqlProjectItemRepository,
|
|
531
|
+
localStorageCacheRepository,
|
|
532
|
+
...githubRepositoryParams,
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
const useCase = new CheckIssueReviewReadinessUseCase(
|
|
536
|
+
projectRepository,
|
|
537
|
+
issueRepository,
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
const result = await useCase.run({
|
|
541
|
+
projectUrl,
|
|
542
|
+
issueUrl: options.issueUrl,
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
546
|
+
});
|
|
547
|
+
|
|
447
548
|
/* istanbul ignore next */
|
|
448
549
|
if (process.argv && require.main === module) {
|
|
449
550
|
program.parse(process.argv);
|
|
@@ -49,6 +49,24 @@ const extractRequestedFirstFromMockCall = (
|
|
|
49
49
|
return typeof first === 'number' ? first : undefined;
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
const extractRequestedQueryFromMockCall = (
|
|
53
|
+
call: unknown,
|
|
54
|
+
): string | undefined => {
|
|
55
|
+
if (!Array.isArray(call)) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
const second: unknown = call[1];
|
|
59
|
+
if (!isRecord(second)) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
const json: unknown = second.json;
|
|
63
|
+
if (!isRecord(json)) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
const query: unknown = json.query;
|
|
67
|
+
return typeof query === 'string' ? query : undefined;
|
|
68
|
+
};
|
|
69
|
+
|
|
52
70
|
const extractErrorMessage = (value: unknown): string => {
|
|
53
71
|
if (value instanceof Error) {
|
|
54
72
|
return value.message;
|
|
@@ -477,4 +495,170 @@ describe('GraphqlProjectItemRepository', () => {
|
|
|
477
495
|
]);
|
|
478
496
|
});
|
|
479
497
|
});
|
|
498
|
+
|
|
499
|
+
describe('fetchProjectItemByUrl', () => {
|
|
500
|
+
afterEach(() => {
|
|
501
|
+
mockPost.mockClear();
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
const makeContentNode = (url: string, number: number, title: string) => ({
|
|
505
|
+
number,
|
|
506
|
+
title,
|
|
507
|
+
state: 'OPEN',
|
|
508
|
+
url,
|
|
509
|
+
body: 'body text',
|
|
510
|
+
createdAt: '2024-01-01T00:00:00Z',
|
|
511
|
+
author: { login: 'octocat' },
|
|
512
|
+
labels: { nodes: [{ name: 'bug' }] },
|
|
513
|
+
assignees: { nodes: [{ login: 'octocat' }] },
|
|
514
|
+
repository: { nameWithOwner: 'owner/repo' },
|
|
515
|
+
projectItems: {
|
|
516
|
+
nodes: [
|
|
517
|
+
{
|
|
518
|
+
id: `item-${number}`,
|
|
519
|
+
fieldValues: {
|
|
520
|
+
nodes: [
|
|
521
|
+
{
|
|
522
|
+
__typename: 'ProjectV2ItemFieldSingleSelectValue',
|
|
523
|
+
name: 'Preparation',
|
|
524
|
+
field: { name: 'Status' },
|
|
525
|
+
},
|
|
526
|
+
],
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
],
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it('should return project item for an issue URL', async () => {
|
|
534
|
+
const localStorageRepository = new LocalStorageRepository();
|
|
535
|
+
const repository = new GraphqlProjectItemRepository(
|
|
536
|
+
localStorageRepository,
|
|
537
|
+
'dummy-token',
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
mockPost.mockReturnValueOnce(
|
|
541
|
+
mockJsonResponse({
|
|
542
|
+
data: {
|
|
543
|
+
repository: {
|
|
544
|
+
issue: makeContentNode(
|
|
545
|
+
'https://github.com/owner/repo/issues/7',
|
|
546
|
+
7,
|
|
547
|
+
'Issue Title',
|
|
548
|
+
),
|
|
549
|
+
pullRequest: null,
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
}),
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
const result = await repository.fetchProjectItemByUrl(
|
|
556
|
+
'https://github.com/owner/repo/issues/7',
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
expect(mockPost).toHaveBeenCalledTimes(1);
|
|
560
|
+
expect(result).not.toBeNull();
|
|
561
|
+
expect(result?.url).toBe('https://github.com/owner/repo/issues/7');
|
|
562
|
+
expect(result?.title).toBe('Issue Title');
|
|
563
|
+
expect(result?.id).toBe('item-7');
|
|
564
|
+
expect(result?.customFields).toEqual([
|
|
565
|
+
{ name: 'Status', value: 'Preparation' },
|
|
566
|
+
]);
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
it('should return project item for a pull request URL when repository.issue is null', async () => {
|
|
570
|
+
const localStorageRepository = new LocalStorageRepository();
|
|
571
|
+
const repository = new GraphqlProjectItemRepository(
|
|
572
|
+
localStorageRepository,
|
|
573
|
+
'dummy-token',
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
mockPost.mockReturnValueOnce(
|
|
577
|
+
mockJsonResponse({
|
|
578
|
+
data: {
|
|
579
|
+
repository: {
|
|
580
|
+
issue: null,
|
|
581
|
+
pullRequest: makeContentNode(
|
|
582
|
+
'https://github.com/owner/repo/pull/9',
|
|
583
|
+
9,
|
|
584
|
+
'PR Title',
|
|
585
|
+
),
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
}),
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
const result = await repository.fetchProjectItemByUrl(
|
|
592
|
+
'https://github.com/owner/repo/pull/9',
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
expect(mockPost).toHaveBeenCalledTimes(1);
|
|
596
|
+
expect(result).not.toBeNull();
|
|
597
|
+
expect(result?.url).toBe('https://github.com/owner/repo/pull/9');
|
|
598
|
+
expect(result?.title).toBe('PR Title');
|
|
599
|
+
expect(result?.id).toBe('item-9');
|
|
600
|
+
expect(result?.customFields).toEqual([
|
|
601
|
+
{ name: 'Status', value: 'Preparation' },
|
|
602
|
+
]);
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it('should query both issue and pullRequest fields in a single request', async () => {
|
|
606
|
+
const localStorageRepository = new LocalStorageRepository();
|
|
607
|
+
const repository = new GraphqlProjectItemRepository(
|
|
608
|
+
localStorageRepository,
|
|
609
|
+
'dummy-token',
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
mockPost.mockReturnValueOnce(
|
|
613
|
+
mockJsonResponse({
|
|
614
|
+
data: {
|
|
615
|
+
repository: {
|
|
616
|
+
issue: null,
|
|
617
|
+
pullRequest: makeContentNode(
|
|
618
|
+
'https://github.com/owner/repo/pull/3',
|
|
619
|
+
3,
|
|
620
|
+
'PR Title',
|
|
621
|
+
),
|
|
622
|
+
},
|
|
623
|
+
},
|
|
624
|
+
}),
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
await repository.fetchProjectItemByUrl(
|
|
628
|
+
'https://github.com/owner/repo/pull/3',
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
const sentQuery = extractRequestedQueryFromMockCall(
|
|
632
|
+
mockPost.mock.calls[0],
|
|
633
|
+
);
|
|
634
|
+
expect(typeof sentQuery).toBe('string');
|
|
635
|
+
expect(sentQuery).toContain('issue(number: $number)');
|
|
636
|
+
expect(sentQuery).toContain('pullRequest(number: $number)');
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it('should return null when neither issue nor pullRequest exists', async () => {
|
|
640
|
+
const localStorageRepository = new LocalStorageRepository();
|
|
641
|
+
const repository = new GraphqlProjectItemRepository(
|
|
642
|
+
localStorageRepository,
|
|
643
|
+
'dummy-token',
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
mockPost.mockReturnValueOnce(
|
|
647
|
+
mockJsonResponse({
|
|
648
|
+
data: {
|
|
649
|
+
repository: {
|
|
650
|
+
issue: null,
|
|
651
|
+
pullRequest: null,
|
|
652
|
+
},
|
|
653
|
+
},
|
|
654
|
+
}),
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
const result = await repository.fetchProjectItemByUrl(
|
|
658
|
+
'https://github.com/owner/repo/issues/123',
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
expect(result).toBeNull();
|
|
662
|
+
});
|
|
663
|
+
});
|
|
480
664
|
});
|
|
@@ -665,7 +665,7 @@ query GetProjectFields($owner: String!, $repository: String!, $issueNumber: Int!
|
|
|
665
665
|
issueUrl: string,
|
|
666
666
|
): Promise<ProjectItem | null> => {
|
|
667
667
|
const { owner, repo, issueNumber } = this.extractIssueFromUrl(issueUrl);
|
|
668
|
-
const graphql = `query
|
|
668
|
+
const graphql = `query GetIssueOrPullRequest($owner: String!, $repo: String!, $number: Int!) {
|
|
669
669
|
repository(owner: $owner, name: $repo) {
|
|
670
670
|
issue(number: $number) {
|
|
671
671
|
number
|
|
@@ -741,6 +741,80 @@ query GetProjectFields($owner: String!, $repository: String!, $issueNumber: Int!
|
|
|
741
741
|
}
|
|
742
742
|
}
|
|
743
743
|
}
|
|
744
|
+
pullRequest(number: $number) {
|
|
745
|
+
number
|
|
746
|
+
title
|
|
747
|
+
state
|
|
748
|
+
url
|
|
749
|
+
body
|
|
750
|
+
createdAt
|
|
751
|
+
author {
|
|
752
|
+
login
|
|
753
|
+
}
|
|
754
|
+
labels(first: 100) {
|
|
755
|
+
nodes {
|
|
756
|
+
name
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
assignees(first: 20) {
|
|
760
|
+
nodes {
|
|
761
|
+
login
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
repository {
|
|
765
|
+
nameWithOwner
|
|
766
|
+
}
|
|
767
|
+
projectItems(first: 10) {
|
|
768
|
+
nodes {
|
|
769
|
+
id
|
|
770
|
+
fieldValues(first: 10) {
|
|
771
|
+
nodes {
|
|
772
|
+
... on ProjectV2ItemFieldTextValue {
|
|
773
|
+
text
|
|
774
|
+
field {
|
|
775
|
+
... on ProjectV2Field {
|
|
776
|
+
name
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
... on ProjectV2ItemFieldNumberValue {
|
|
781
|
+
number
|
|
782
|
+
id
|
|
783
|
+
field {
|
|
784
|
+
... on ProjectV2Field {
|
|
785
|
+
name
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
... on ProjectV2ItemFieldDateValue {
|
|
790
|
+
date
|
|
791
|
+
field {
|
|
792
|
+
... on ProjectV2Field {
|
|
793
|
+
name
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
... on ProjectV2ItemFieldSingleSelectValue {
|
|
798
|
+
name
|
|
799
|
+
field {
|
|
800
|
+
... on ProjectV2SingleSelectField {
|
|
801
|
+
name
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
... on ProjectV2ItemFieldIterationValue {
|
|
806
|
+
title
|
|
807
|
+
field {
|
|
808
|
+
... on ProjectV2Field {
|
|
809
|
+
name
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
744
818
|
}
|
|
745
819
|
}`;
|
|
746
820
|
const graphqlQuery = {
|
|
@@ -751,6 +825,34 @@ query GetProjectFields($owner: String!, $repository: String!, $issueNumber: Int!
|
|
|
751
825
|
number: issueNumber,
|
|
752
826
|
},
|
|
753
827
|
};
|
|
828
|
+
type ContentNode = {
|
|
829
|
+
number: number;
|
|
830
|
+
title: string;
|
|
831
|
+
state: string;
|
|
832
|
+
url: string;
|
|
833
|
+
body: string;
|
|
834
|
+
createdAt: string;
|
|
835
|
+
author: { login: string } | null;
|
|
836
|
+
labels: { nodes: { name: string }[] };
|
|
837
|
+
assignees: { nodes: { login: string }[] };
|
|
838
|
+
repository: { nameWithOwner: string };
|
|
839
|
+
projectItems: {
|
|
840
|
+
nodes: {
|
|
841
|
+
id: string;
|
|
842
|
+
fieldValues: {
|
|
843
|
+
nodes: {
|
|
844
|
+
text: string;
|
|
845
|
+
number: number;
|
|
846
|
+
date: string;
|
|
847
|
+
name: string;
|
|
848
|
+
field: {
|
|
849
|
+
name: string;
|
|
850
|
+
};
|
|
851
|
+
}[];
|
|
852
|
+
};
|
|
853
|
+
}[];
|
|
854
|
+
};
|
|
855
|
+
};
|
|
754
856
|
const response = await ky
|
|
755
857
|
.post('https://api.github.com/graphql', {
|
|
756
858
|
json: graphqlQuery,
|
|
@@ -761,34 +863,8 @@ query GetProjectFields($owner: String!, $repository: String!, $issueNumber: Int!
|
|
|
761
863
|
.json<{
|
|
762
864
|
data?: {
|
|
763
865
|
repository: {
|
|
764
|
-
issue:
|
|
765
|
-
|
|
766
|
-
title: string;
|
|
767
|
-
state: string;
|
|
768
|
-
url: string;
|
|
769
|
-
body: string;
|
|
770
|
-
createdAt: string;
|
|
771
|
-
author: { login: string } | null;
|
|
772
|
-
labels: { nodes: { name: string }[] };
|
|
773
|
-
assignees: { nodes: { login: string }[] };
|
|
774
|
-
repository: { nameWithOwner: string };
|
|
775
|
-
projectItems: {
|
|
776
|
-
nodes: {
|
|
777
|
-
id: string;
|
|
778
|
-
fieldValues: {
|
|
779
|
-
nodes: {
|
|
780
|
-
text: string;
|
|
781
|
-
number: number;
|
|
782
|
-
date: string;
|
|
783
|
-
name: string;
|
|
784
|
-
field: {
|
|
785
|
-
name: string;
|
|
786
|
-
};
|
|
787
|
-
}[];
|
|
788
|
-
};
|
|
789
|
-
}[];
|
|
790
|
-
};
|
|
791
|
-
};
|
|
866
|
+
issue: ContentNode | null;
|
|
867
|
+
pullRequest: ContentNode | null;
|
|
792
868
|
};
|
|
793
869
|
};
|
|
794
870
|
errors?: { message: string }[];
|
|
@@ -802,40 +878,27 @@ query GetProjectFields($owner: String!, $repository: String!, $issueNumber: Int!
|
|
|
802
878
|
);
|
|
803
879
|
}
|
|
804
880
|
const data = response.data;
|
|
805
|
-
|
|
881
|
+
const content = data.repository.issue ?? data.repository.pullRequest;
|
|
882
|
+
if (!content) {
|
|
806
883
|
return null;
|
|
807
884
|
}
|
|
808
|
-
const projectItems
|
|
809
|
-
id: string;
|
|
810
|
-
fieldValues: {
|
|
811
|
-
nodes: {
|
|
812
|
-
text: string;
|
|
813
|
-
number: number;
|
|
814
|
-
date: string;
|
|
815
|
-
name: string;
|
|
816
|
-
field: {
|
|
817
|
-
name: string;
|
|
818
|
-
};
|
|
819
|
-
}[];
|
|
820
|
-
};
|
|
821
|
-
}[] = data.repository.issue.projectItems.nodes;
|
|
885
|
+
const projectItems = content.projectItems.nodes;
|
|
822
886
|
const item = projectItems[0];
|
|
823
887
|
if (!item) {
|
|
824
888
|
throw new Error(`No project item found for issue ${issueUrl}`);
|
|
825
889
|
}
|
|
826
890
|
return {
|
|
827
891
|
id: item.id,
|
|
828
|
-
nameWithOwner:
|
|
829
|
-
number:
|
|
830
|
-
title:
|
|
831
|
-
state: this.convertStrToState(
|
|
832
|
-
url:
|
|
833
|
-
body:
|
|
834
|
-
labels:
|
|
835
|
-
assignees:
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
author: data.repository.issue.author?.login || '',
|
|
892
|
+
nameWithOwner: content.repository.nameWithOwner,
|
|
893
|
+
number: content.number,
|
|
894
|
+
title: content.title,
|
|
895
|
+
state: this.convertStrToState(content.state),
|
|
896
|
+
url: content.url,
|
|
897
|
+
body: content.body,
|
|
898
|
+
labels: content.labels?.nodes?.map((l) => l.name) || [],
|
|
899
|
+
assignees: content.assignees?.nodes?.map((a) => a.login) || [],
|
|
900
|
+
createdAt: content.createdAt || new Date().toISOString(),
|
|
901
|
+
author: content.author?.login || '',
|
|
839
902
|
customFields: item.fieldValues.nodes
|
|
840
903
|
.filter((field) => !!field.field)
|
|
841
904
|
.map((field) => {
|