@gitkraken/core-gitlens 0.3.1 → 0.4.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.
- package/CHANGELOG.md +13 -1
- package/README.md +13 -10
- package/dist/git/context.d.ts +4 -3
- package/dist/git/context.d.ts.map +1 -1
- package/dist/git/context.js.map +1 -1
- package/dist/git/providers/operations.d.ts +1 -0
- package/dist/git/providers/operations.d.ts.map +1 -1
- package/dist/git/providers/worktrees.d.ts +2 -0
- package/dist/git/providers/worktrees.d.ts.map +1 -1
- package/dist/git/utils/conflictResolution.utils.d.ts +32 -0
- package/dist/git/utils/conflictResolution.utils.d.ts.map +1 -1
- package/dist/git/utils/conflictResolution.utils.js +95 -0
- package/dist/git/utils/conflictResolution.utils.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts +1 -0
- package/dist/git-cli/providers/operations.d.ts.map +1 -1
- package/dist/git-cli/providers/operations.js +22 -1
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/worktrees.d.ts +2 -0
- package/dist/git-cli/providers/worktrees.d.ts.map +1 -1
- package/dist/git-cli/providers/worktrees.js +3 -0
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/plus/ai/utils/ai.utils.js +5 -5
- package/dist/plus/ai/utils/ai.utils.js.map +1 -1
- package/dist/plus/git-github/api/github.d.ts +6 -13
- package/dist/plus/git-github/api/github.d.ts.map +1 -1
- package/dist/plus/git-github/api/github.js +5 -10
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/integrations/authentication/cloudIntegrationService.d.ts +11 -0
- package/dist/plus/integrations/authentication/cloudIntegrationService.d.ts.map +1 -0
- package/dist/plus/integrations/authentication/cloudIntegrationService.js +90 -0
- package/dist/plus/integrations/authentication/cloudIntegrationService.js.map +1 -0
- package/dist/plus/integrations/authentication/configuredIntegrationService.d.ts +67 -0
- package/dist/plus/integrations/authentication/configuredIntegrationService.d.ts.map +1 -0
- package/dist/plus/integrations/authentication/configuredIntegrationService.js +295 -0
- package/dist/plus/integrations/authentication/configuredIntegrationService.js.map +1 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.d.ts +92 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.d.ts.map +1 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js +185 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js.map +1 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationService.d.ts +62 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationService.d.ts.map +1 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationService.js +122 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationService.js.map +1 -0
- package/dist/plus/integrations/authentication/manualTokenProvider.d.ts +15 -0
- package/dist/plus/integrations/authentication/manualTokenProvider.d.ts.map +1 -0
- package/dist/plus/integrations/authentication/manualTokenProvider.js +49 -0
- package/dist/plus/integrations/authentication/manualTokenProvider.js.map +1 -0
- package/dist/plus/integrations/authentication/models.d.ts +87 -0
- package/dist/plus/integrations/authentication/models.d.ts.map +1 -0
- package/dist/plus/integrations/authentication/models.js +64 -0
- package/dist/plus/integrations/authentication/models.js.map +1 -0
- package/dist/plus/integrations/constants.d.ts +47 -0
- package/dist/plus/integrations/constants.d.ts.map +1 -0
- package/dist/plus/integrations/constants.js +128 -0
- package/dist/plus/integrations/constants.js.map +1 -0
- package/dist/plus/integrations/context.d.ts +354 -0
- package/dist/plus/integrations/context.d.ts.map +1 -0
- package/dist/plus/integrations/context.js +2 -0
- package/dist/plus/integrations/context.js.map +1 -0
- package/dist/plus/integrations/errors.d.ts +14 -0
- package/dist/plus/integrations/errors.d.ts.map +1 -0
- package/dist/plus/integrations/errors.js +19 -0
- package/dist/plus/integrations/errors.js.map +1 -0
- package/dist/plus/integrations/index.d.ts +32 -0
- package/dist/plus/integrations/index.d.ts.map +1 -0
- package/dist/plus/integrations/index.js +62 -0
- package/dist/plus/integrations/index.js.map +1 -0
- package/dist/plus/integrations/integrationService.d.ts +98 -0
- package/dist/plus/integrations/integrationService.d.ts.map +1 -0
- package/dist/plus/integrations/integrationService.js +627 -0
- package/dist/plus/integrations/integrationService.js.map +1 -0
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +82 -0
- package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -0
- package/dist/plus/integrations/models/gitHostIntegration.js +522 -0
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -0
- package/dist/plus/integrations/models/integration.d.ts +127 -0
- package/dist/plus/integrations/models/integration.d.ts.map +1 -0
- package/dist/plus/integrations/models/integration.js +456 -0
- package/dist/plus/integrations/models/integration.js.map +1 -0
- package/dist/plus/integrations/models/issuesIntegration.d.ts +28 -0
- package/dist/plus/integrations/models/issuesIntegration.d.ts.map +1 -0
- package/dist/plus/integrations/models/issuesIntegration.js +101 -0
- package/dist/plus/integrations/models/issuesIntegration.js.map +1 -0
- package/dist/plus/integrations/providers/apiClients.d.ts +25 -0
- package/dist/plus/integrations/providers/apiClients.d.ts.map +1 -0
- package/dist/plus/integrations/providers/apiClients.js +39 -0
- package/dist/plus/integrations/providers/apiClients.js.map +1 -0
- package/dist/plus/integrations/providers/azure/azure.d.ts +47 -0
- package/dist/plus/integrations/providers/azure/azure.d.ts.map +1 -0
- package/dist/plus/integrations/providers/azure/azure.js +444 -0
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -0
- package/dist/plus/integrations/providers/azure/models.d.ts +286 -0
- package/dist/plus/integrations/providers/azure/models.d.ts.map +1 -0
- package/dist/plus/integrations/providers/azure/models.js +203 -0
- package/dist/plus/integrations/providers/azure/models.js.map +1 -0
- package/dist/plus/integrations/providers/azureDevOps.d.ts +95 -0
- package/dist/plus/integrations/providers/azureDevOps.d.ts.map +1 -0
- package/dist/plus/integrations/providers/azureDevOps.js +432 -0
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -0
- package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +42 -0
- package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts.map +1 -0
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js +513 -0
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -0
- package/dist/plus/integrations/providers/bitbucket/models.d.ts +222 -0
- package/dist/plus/integrations/providers/bitbucket/models.d.ts.map +1 -0
- package/dist/plus/integrations/providers/bitbucket/models.js +158 -0
- package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -0
- package/dist/plus/integrations/providers/bitbucket-server/models.d.ts +120 -0
- package/dist/plus/integrations/providers/bitbucket-server/models.d.ts.map +1 -0
- package/dist/plus/integrations/providers/bitbucket-server/models.js +111 -0
- package/dist/plus/integrations/providers/bitbucket-server/models.js.map +1 -0
- package/dist/plus/integrations/providers/bitbucket-server.d.ts +60 -0
- package/dist/plus/integrations/providers/bitbucket-server.d.ts.map +1 -0
- package/dist/plus/integrations/providers/bitbucket-server.js +138 -0
- package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -0
- package/dist/plus/integrations/providers/bitbucket.d.ts +50 -0
- package/dist/plus/integrations/providers/bitbucket.d.ts.map +1 -0
- package/dist/plus/integrations/providers/bitbucket.js +181 -0
- package/dist/plus/integrations/providers/bitbucket.js.map +1 -0
- package/dist/plus/integrations/providers/github/github.d.ts +5 -0
- package/dist/plus/integrations/providers/github/github.d.ts.map +1 -0
- package/dist/plus/integrations/providers/github/github.js +47 -0
- package/dist/plus/integrations/providers/github/github.js.map +1 -0
- package/dist/plus/integrations/providers/github/github.utils.d.ts +11 -0
- package/dist/plus/integrations/providers/github/github.utils.d.ts.map +1 -0
- package/dist/plus/integrations/providers/github/github.utils.js +6 -0
- package/dist/plus/integrations/providers/github/github.utils.js.map +1 -0
- package/dist/plus/integrations/providers/github/models.d.ts +3 -0
- package/dist/plus/integrations/providers/github/models.d.ts.map +1 -0
- package/dist/plus/integrations/providers/github/models.js +2 -0
- package/dist/plus/integrations/providers/github/models.js.map +1 -0
- package/dist/plus/integrations/providers/github.d.ts +73 -0
- package/dist/plus/integrations/providers/github.d.ts.map +1 -0
- package/dist/plus/integrations/providers/github.js +160 -0
- package/dist/plus/integrations/providers/github.js.map +1 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +63 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.d.ts.map +1 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.js +828 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.utils.d.ts +21 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.utils.d.ts.map +1 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.utils.js +45 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.utils.js.map +1 -0
- package/dist/plus/integrations/providers/gitlab/models.d.ts +128 -0
- package/dist/plus/integrations/providers/gitlab/models.d.ts.map +1 -0
- package/dist/plus/integrations/providers/gitlab/models.js +96 -0
- package/dist/plus/integrations/providers/gitlab/models.js.map +1 -0
- package/dist/plus/integrations/providers/gitlab.d.ts +79 -0
- package/dist/plus/integrations/providers/gitlab.d.ts.map +1 -0
- package/dist/plus/integrations/providers/gitlab.js +253 -0
- package/dist/plus/integrations/providers/gitlab.js.map +1 -0
- package/dist/plus/integrations/providers/jira.d.ts +46 -0
- package/dist/plus/integrations/providers/jira.d.ts.map +1 -0
- package/dist/plus/integrations/providers/jira.js +255 -0
- package/dist/plus/integrations/providers/jira.js.map +1 -0
- package/dist/plus/integrations/providers/linear.d.ts +47 -0
- package/dist/plus/integrations/providers/linear.d.ts.map +1 -0
- package/dist/plus/integrations/providers/linear.js +198 -0
- package/dist/plus/integrations/providers/linear.js.map +1 -0
- package/dist/plus/integrations/providers/models.d.ts +433 -0
- package/dist/plus/integrations/providers/models.d.ts.map +1 -0
- package/dist/plus/integrations/providers/models.js +602 -0
- package/dist/plus/integrations/providers/models.js.map +1 -0
- package/dist/plus/integrations/providers/providersApi.d.ts +129 -0
- package/dist/plus/integrations/providers/providersApi.d.ts.map +1 -0
- package/dist/plus/integrations/providers/providersApi.js +602 -0
- package/dist/plus/integrations/providers/providersApi.js.map +1 -0
- package/dist/plus/integrations/providers/utils.d.ts +54 -0
- package/dist/plus/integrations/providers/utils.d.ts.map +1 -0
- package/dist/plus/integrations/providers/utils.js +262 -0
- package/dist/plus/integrations/providers/utils.js.map +1 -0
- package/dist/plus/integrations/telemetry.d.ts +26 -0
- package/dist/plus/integrations/telemetry.d.ts.map +1 -0
- package/dist/plus/integrations/telemetry.js +11 -0
- package/dist/plus/integrations/telemetry.js.map +1 -0
- package/dist/plus/integrations/utils/integration.utils.d.ts +14 -0
- package/dist/plus/integrations/utils/integration.utils.d.ts.map +1 -0
- package/dist/plus/integrations/utils/integration.utils.js +111 -0
- package/dist/plus/integrations/utils/integration.utils.js.map +1 -0
- package/dist/utils/disposable.d.ts +15 -2
- package/dist/utils/disposable.d.ts.map +1 -1
- package/dist/utils/disposable.js.map +1 -1
- package/package.json +43 -2
- package/src/git/context.ts +4 -3
- package/src/git/providers/operations.ts +1 -1
- package/src/git/providers/worktrees.ts +14 -2
- package/src/git/utils/conflictResolution.utils.ts +126 -0
- package/src/git-cli/providers/operations.ts +22 -2
- package/src/git-cli/providers/worktrees.ts +17 -2
- package/src/plus/ai/utils/ai.utils.ts +5 -5
- package/src/plus/git-github/api/github.ts +13 -25
- package/src/plus/integrations/authentication/cloudIntegrationService.ts +120 -0
- package/src/plus/integrations/authentication/configuredIntegrationService.ts +414 -0
- package/src/plus/integrations/authentication/integrationAuthenticationProvider.ts +289 -0
- package/src/plus/integrations/authentication/integrationAuthenticationService.ts +144 -0
- package/src/plus/integrations/authentication/manualTokenProvider.ts +72 -0
- package/src/plus/integrations/authentication/models.ts +168 -0
- package/src/plus/integrations/constants.ts +169 -0
- package/src/plus/integrations/context.ts +383 -0
- package/src/plus/integrations/errors.ts +35 -0
- package/src/plus/integrations/index.ts +119 -0
- package/src/plus/integrations/integrationService.ts +924 -0
- package/src/plus/integrations/models/gitHostIntegration.ts +738 -0
- package/src/plus/integrations/models/integration.ts +657 -0
- package/src/plus/integrations/models/issuesIntegration.ts +124 -0
- package/src/plus/integrations/providers/apiClients.ts +72 -0
- package/src/plus/integrations/providers/azure/azure.ts +691 -0
- package/src/plus/integrations/providers/azure/models.ts +605 -0
- package/src/plus/integrations/providers/azureDevOps.ts +677 -0
- package/src/plus/integrations/providers/bitbucket/bitbucket.ts +774 -0
- package/src/plus/integrations/providers/bitbucket/models.ts +445 -0
- package/src/plus/integrations/providers/bitbucket-server/models.ts +252 -0
- package/src/plus/integrations/providers/bitbucket-server.ts +282 -0
- package/src/plus/integrations/providers/bitbucket.ts +350 -0
- package/src/plus/integrations/providers/github/github.ts +64 -0
- package/src/plus/integrations/providers/github/github.utils.ts +23 -0
- package/src/plus/integrations/providers/github/models.ts +29 -0
- package/src/plus/integrations/providers/github.ts +366 -0
- package/src/plus/integrations/providers/gitlab/gitlab.ts +1210 -0
- package/src/plus/integrations/providers/gitlab/gitlab.utils.ts +71 -0
- package/src/plus/integrations/providers/gitlab/models.ts +257 -0
- package/src/plus/integrations/providers/gitlab.ts +473 -0
- package/src/plus/integrations/providers/jira.ts +359 -0
- package/src/plus/integrations/providers/linear.ts +275 -0
- package/src/plus/integrations/providers/models.ts +1114 -0
- package/src/plus/integrations/providers/providersApi.ts +1126 -0
- package/src/plus/integrations/providers/utils.ts +359 -0
- package/src/plus/integrations/telemetry.ts +29 -0
- package/src/plus/integrations/utils/integration.utils.ts +138 -0
- package/src/utils/disposable.ts +15 -2
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
import type { IssueMember } from '../../../../git/models/issue.js';
|
|
2
|
+
import { Issue, RepositoryAccessLevel } from '../../../../git/models/issue.js';
|
|
3
|
+
import type { IssueOrPullRequestState } from '../../../../git/models/issueOrPullRequest.js';
|
|
4
|
+
import type { PullRequestMember, PullRequestReviewer } from '../../../../git/models/pullRequest.js';
|
|
5
|
+
import {
|
|
6
|
+
PullRequest,
|
|
7
|
+
PullRequestMergeableState,
|
|
8
|
+
PullRequestReviewDecision,
|
|
9
|
+
PullRequestReviewState,
|
|
10
|
+
} from '../../../../git/models/pullRequest.js';
|
|
11
|
+
import type { Provider } from '../../../../git/models/remoteProvider.js';
|
|
12
|
+
import type { ResourceDescriptor } from '../../../../git/models/resourceDescriptor.js';
|
|
13
|
+
|
|
14
|
+
const vstsHostnameRegex = /\.visualstudio\.com$/;
|
|
15
|
+
|
|
16
|
+
export interface AzureRepositoryDescriptor extends ResourceDescriptor {
|
|
17
|
+
owner: string;
|
|
18
|
+
name: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface AzureOrganizationDescriptor extends ResourceDescriptor {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface AzureProjectDescriptor extends ResourceDescriptor {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
resourceId: string;
|
|
30
|
+
resourceName: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface AzureRemoteRepositoryDescriptor extends ResourceDescriptor {
|
|
34
|
+
id: string;
|
|
35
|
+
nodeId?: string;
|
|
36
|
+
resourceName: string;
|
|
37
|
+
name: string;
|
|
38
|
+
projectName?: string;
|
|
39
|
+
url?: string;
|
|
40
|
+
cloneUrlHttps?: string;
|
|
41
|
+
cloneUrlSsh?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface AzureProjectInputDescriptor extends ResourceDescriptor {
|
|
45
|
+
owner: string;
|
|
46
|
+
name: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type AzureWorkItemStateCategory = 'Proposed' | 'InProgress' | 'Resolved' | 'Completed' | 'Removed';
|
|
50
|
+
|
|
51
|
+
export function isClosedAzureWorkItemStateCategory(category: AzureWorkItemStateCategory | undefined): boolean {
|
|
52
|
+
return category === 'Completed' || category === 'Resolved' || category === 'Removed';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function azureWorkItemsStateCategoryToState(
|
|
56
|
+
category: AzureWorkItemStateCategory | undefined,
|
|
57
|
+
): IssueOrPullRequestState {
|
|
58
|
+
switch (category) {
|
|
59
|
+
case 'Resolved':
|
|
60
|
+
case 'Completed':
|
|
61
|
+
case 'Removed':
|
|
62
|
+
return 'closed';
|
|
63
|
+
case 'Proposed':
|
|
64
|
+
case 'InProgress':
|
|
65
|
+
default:
|
|
66
|
+
return 'opened';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface AzureLink {
|
|
71
|
+
href: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface AzureUser {
|
|
75
|
+
displayName: string;
|
|
76
|
+
url: string;
|
|
77
|
+
_links: {
|
|
78
|
+
avatar: AzureLink;
|
|
79
|
+
};
|
|
80
|
+
id: string;
|
|
81
|
+
uniqueName: string;
|
|
82
|
+
imageUrl: string;
|
|
83
|
+
descriptor?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface AzureUserWithVote extends AzureUser {
|
|
87
|
+
isFlagged?: boolean;
|
|
88
|
+
hasDeclined?: boolean;
|
|
89
|
+
isReapprove?: boolean;
|
|
90
|
+
isRequired?: boolean;
|
|
91
|
+
vote?: AzurePullRequestVote;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type AzurePullRequestVote =
|
|
95
|
+
| 10 // approved
|
|
96
|
+
| 5 // approved with suggestions
|
|
97
|
+
| 0 // no vote
|
|
98
|
+
| -5 // waiting for author
|
|
99
|
+
| -10; // rejected
|
|
100
|
+
|
|
101
|
+
export interface AzureWorkItemCommentVersionRef {
|
|
102
|
+
commentId: number;
|
|
103
|
+
createdInRevision: number;
|
|
104
|
+
isDeleted: boolean;
|
|
105
|
+
text: string;
|
|
106
|
+
url: string;
|
|
107
|
+
version: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface AzureWorkItemRelation {
|
|
111
|
+
attributes: {
|
|
112
|
+
[key: string]: string;
|
|
113
|
+
};
|
|
114
|
+
relation: string;
|
|
115
|
+
url: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface WorkItem {
|
|
119
|
+
_links: {
|
|
120
|
+
fields: AzureLink;
|
|
121
|
+
html: AzureLink;
|
|
122
|
+
self: AzureLink;
|
|
123
|
+
workItemComments: AzureLink;
|
|
124
|
+
workItemRevisions: AzureLink;
|
|
125
|
+
workItemType: AzureLink;
|
|
126
|
+
workItemUpdates: AzureLink;
|
|
127
|
+
};
|
|
128
|
+
fields: {
|
|
129
|
+
//'System.AreaPath': string;
|
|
130
|
+
'System.TeamProject': string;
|
|
131
|
+
// 'System.IterationPath': string;
|
|
132
|
+
'System.WorkItemType': string;
|
|
133
|
+
'System.State': string;
|
|
134
|
+
// 'System.Reason': string;
|
|
135
|
+
'System.AssignedTo': AzureUser;
|
|
136
|
+
'System.CreatedDate': string;
|
|
137
|
+
'System.CreatedBy': AzureUser;
|
|
138
|
+
'System.ChangedDate': string;
|
|
139
|
+
'System.ChangedBy': AzureUser;
|
|
140
|
+
'System.CommentCount': number;
|
|
141
|
+
'System.Description': string;
|
|
142
|
+
'System.Title': string;
|
|
143
|
+
'Microsoft.VSTS.Common.ClosedDate': string;
|
|
144
|
+
// 'Microsoft.VSTS.Common.StateChangeDate': string;
|
|
145
|
+
// 'Microsoft.VSTS.Common.Priority': number;
|
|
146
|
+
// 'Microsoft.VSTS.Common.Severity': string;
|
|
147
|
+
// 'Microsoft.VSTS.Common.ValueArea': string;
|
|
148
|
+
};
|
|
149
|
+
id: number;
|
|
150
|
+
rev: number;
|
|
151
|
+
url: string;
|
|
152
|
+
commentVersionRef?: AzureWorkItemCommentVersionRef;
|
|
153
|
+
relations?: AzureWorkItemRelation[];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface AzureWorkItemState {
|
|
157
|
+
name: string;
|
|
158
|
+
color: string;
|
|
159
|
+
category: AzureWorkItemStateCategory;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export type AzurePullRequestStatus = 'abandoned' | 'active' | 'completed' | 'notSet';
|
|
163
|
+
export function azurePullRequestStatusToState(status: AzurePullRequestStatus): IssueOrPullRequestState {
|
|
164
|
+
switch (status) {
|
|
165
|
+
case 'abandoned':
|
|
166
|
+
return 'closed';
|
|
167
|
+
case 'completed':
|
|
168
|
+
return 'merged';
|
|
169
|
+
case 'active':
|
|
170
|
+
case 'notSet':
|
|
171
|
+
default:
|
|
172
|
+
return 'opened';
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
export function isClosedAzurePullRequestStatus(status: AzurePullRequestStatus): boolean {
|
|
176
|
+
return azurePullRequestStatusToState(status) !== 'opened';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export type AzureProjectState = 'createPending' | 'deleted' | 'deleting' | 'new' | 'unchanged' | 'wellFormed';
|
|
180
|
+
export type AzureProjectVisibility = 'private' | 'public';
|
|
181
|
+
|
|
182
|
+
export interface AzureProject {
|
|
183
|
+
id: string;
|
|
184
|
+
name: string;
|
|
185
|
+
url: string;
|
|
186
|
+
state: AzureProjectState;
|
|
187
|
+
revision: number;
|
|
188
|
+
visibility: AzureProjectVisibility;
|
|
189
|
+
lastUpdateTime: string;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface AzureRepository {
|
|
193
|
+
id: string;
|
|
194
|
+
name: string;
|
|
195
|
+
url: string;
|
|
196
|
+
project: AzureProject;
|
|
197
|
+
size: number;
|
|
198
|
+
remoteUrl: string;
|
|
199
|
+
sshUrl: string;
|
|
200
|
+
webUrl: string;
|
|
201
|
+
isDisabled: boolean;
|
|
202
|
+
isInMaintenance: boolean;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export interface AzureGitUser {
|
|
206
|
+
date?: string;
|
|
207
|
+
email?: string;
|
|
208
|
+
imageUrl?: string;
|
|
209
|
+
name: string;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface AzureGitCommitRef {
|
|
213
|
+
commitId: string;
|
|
214
|
+
url: string;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface AzureGitCommit {
|
|
218
|
+
_links: {
|
|
219
|
+
changes: AzureLink;
|
|
220
|
+
repository: AzureLink;
|
|
221
|
+
self: AzureLink;
|
|
222
|
+
web: AzureLink;
|
|
223
|
+
};
|
|
224
|
+
author: AzureGitUser;
|
|
225
|
+
comment: string;
|
|
226
|
+
commentTruncated?: boolean;
|
|
227
|
+
commitId: string;
|
|
228
|
+
commitTooManyChanges?: boolean;
|
|
229
|
+
committer: AzureGitUser;
|
|
230
|
+
parents: string[];
|
|
231
|
+
push: {
|
|
232
|
+
date: string;
|
|
233
|
+
pushedBy: AzureUser;
|
|
234
|
+
pushId: number;
|
|
235
|
+
};
|
|
236
|
+
remoteUrl: string;
|
|
237
|
+
statuses?: AzureGitStatus[];
|
|
238
|
+
treeId: string;
|
|
239
|
+
url: string;
|
|
240
|
+
workItems?: AzureResourceRef[];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export interface AzureResourceRef {
|
|
244
|
+
id: string;
|
|
245
|
+
url: string;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export interface AzurePullRequestCompletionOptions {
|
|
249
|
+
autoCompleteIgnoreConflicts: number[];
|
|
250
|
+
bypassPolicy: boolean;
|
|
251
|
+
bypassReason: string;
|
|
252
|
+
deleteSourceBranch: boolean;
|
|
253
|
+
mergeCommitMessage: string;
|
|
254
|
+
mergeStrategy: 'noFastForward' | 'rebase' | 'rebaseMerge' | 'squash';
|
|
255
|
+
squashMerge: boolean;
|
|
256
|
+
transitionWorkItems: boolean;
|
|
257
|
+
triggeredByAutoComplete: boolean;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export interface AzureGitStatus {
|
|
261
|
+
context: {
|
|
262
|
+
name: string;
|
|
263
|
+
genre: string;
|
|
264
|
+
};
|
|
265
|
+
createdBy: AzureUser;
|
|
266
|
+
createDate: string;
|
|
267
|
+
description: string;
|
|
268
|
+
state: 'error' | 'failed' | 'notApplicable' | 'notSet' | 'pending' | 'succeeded';
|
|
269
|
+
targetUrl: string;
|
|
270
|
+
updateDate: string;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface AzureGitForkRef {
|
|
274
|
+
creator: AzureUser;
|
|
275
|
+
isLocked: boolean;
|
|
276
|
+
isLockedBy: AzureUser;
|
|
277
|
+
name: string;
|
|
278
|
+
objectId: string;
|
|
279
|
+
peeledObjectId: string;
|
|
280
|
+
repository: AzureRepository;
|
|
281
|
+
statuses: AzureGitStatus[];
|
|
282
|
+
url: string;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export interface AzureWebApiTagDefinition {
|
|
286
|
+
active: boolean;
|
|
287
|
+
id: string;
|
|
288
|
+
name: string;
|
|
289
|
+
url: string;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export interface AzureGitPullRequestMergeOptions {
|
|
293
|
+
conflictAuthorshipCommits: boolean;
|
|
294
|
+
detectRenameFalsePositives: boolean;
|
|
295
|
+
disableRenames: boolean;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export type AzurePullRequestAsyncStatus =
|
|
299
|
+
| 'conflicts'
|
|
300
|
+
| 'failure'
|
|
301
|
+
| 'notSet'
|
|
302
|
+
| 'queued'
|
|
303
|
+
| 'rejectedByPolicy'
|
|
304
|
+
| 'succeeded';
|
|
305
|
+
|
|
306
|
+
export interface AzurePullRequest {
|
|
307
|
+
repository: AzureRepository;
|
|
308
|
+
pullRequestId: number;
|
|
309
|
+
codeReviewId: number;
|
|
310
|
+
status: AzurePullRequestStatus;
|
|
311
|
+
createdBy: AzureUser;
|
|
312
|
+
creationDate: string;
|
|
313
|
+
closedDate?: string;
|
|
314
|
+
closedBy?: AzureUser; // Can be missed even if closedDate is presented.
|
|
315
|
+
title: string;
|
|
316
|
+
description: string;
|
|
317
|
+
forkSource?: AzureGitForkRef;
|
|
318
|
+
sourceRefName: string;
|
|
319
|
+
targetRefName: string;
|
|
320
|
+
isDraft: boolean;
|
|
321
|
+
mergeId: string;
|
|
322
|
+
mergeStatus?: AzurePullRequestAsyncStatus;
|
|
323
|
+
lastMergeCommit?: AzureGitCommitRef;
|
|
324
|
+
lastMergeSourceCommit: AzureGitCommitRef;
|
|
325
|
+
lastMergeTargetCommit: AzureGitCommitRef;
|
|
326
|
+
reviewers: AzureUserWithVote[];
|
|
327
|
+
url: string;
|
|
328
|
+
supportsIterations: boolean;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export interface AzurePullRequestWithLinks extends AzurePullRequest {
|
|
332
|
+
_links: {
|
|
333
|
+
self: AzureLink;
|
|
334
|
+
repository: AzureLink;
|
|
335
|
+
workItems: AzureLink;
|
|
336
|
+
sourceBranch: AzureLink;
|
|
337
|
+
targetBranch: AzureLink;
|
|
338
|
+
statuses: AzureLink;
|
|
339
|
+
sourceCommit: AzureLink;
|
|
340
|
+
targetCommit: AzureLink;
|
|
341
|
+
createdBy: AzureLink;
|
|
342
|
+
iterations: AzureLink;
|
|
343
|
+
};
|
|
344
|
+
artifactId: string;
|
|
345
|
+
autoCompleteSetBy?: AzureUser;
|
|
346
|
+
commits?: AzureGitCommitRef[];
|
|
347
|
+
completionOptions?: AzurePullRequestCompletionOptions;
|
|
348
|
+
completionQueueTime?: string;
|
|
349
|
+
hasMultipleMergeBases?: boolean;
|
|
350
|
+
labels?: AzureWebApiTagDefinition[];
|
|
351
|
+
mergeFailureMessage?: string;
|
|
352
|
+
mergeFailureType?: 'caseSensitive' | 'none' | 'objectTooLarge' | 'unknown';
|
|
353
|
+
mergeOptions?: AzureGitPullRequestMergeOptions;
|
|
354
|
+
remoteUrl?: string;
|
|
355
|
+
workItemRefs?: AzureResourceRef[];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function getVSTSOwner(url: URL): string {
|
|
359
|
+
return url.hostname.split('.')[0];
|
|
360
|
+
}
|
|
361
|
+
export function getAzureDevOpsOwner(url: URL): string {
|
|
362
|
+
return url.pathname.split('/')[1];
|
|
363
|
+
}
|
|
364
|
+
export function getAzureOwner(url: URL): string {
|
|
365
|
+
const isVSTS = vstsHostnameRegex.test(url.hostname);
|
|
366
|
+
return isVSTS ? getVSTSOwner(url) : getAzureDevOpsOwner(url);
|
|
367
|
+
}
|
|
368
|
+
export function isVsts(domain: string): boolean {
|
|
369
|
+
return vstsHostnameRegex.test(domain);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export function getAzureRepo(pr: AzurePullRequest): string {
|
|
373
|
+
return `${pr.repository.project.name}/_git/${pr.repository.name}`;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Example: https://bbbchiv.visualstudio.com/MyFirstProject/_git/test
|
|
377
|
+
const azureProjectRepoRegex = /([^/]+)\/_git\/([^/]+)/;
|
|
378
|
+
function parseVstsHttpsUrl(url: URL): [owner: string, project: string, repo: string] {
|
|
379
|
+
const owner = getVSTSOwner(url);
|
|
380
|
+
const match = azureProjectRepoRegex.exec(url.pathname);
|
|
381
|
+
if (match == null) {
|
|
382
|
+
throw new Error(`Invalid VSTS URL: ${url.toString()}`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const [, project, repo] = match;
|
|
386
|
+
return [owner, project, repo];
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Example https://bbbchiv2@dev.azure.com/bbbchiv2/MyFirstProject/_git/test
|
|
390
|
+
const azureHttpsUrlRegex = /([^/]+)\/([^/]+)\/_git\/([^/]+)/;
|
|
391
|
+
function parseAzureNewStyleUrl(url: URL): [owner: string, project: string, repo: string] {
|
|
392
|
+
const match = azureHttpsUrlRegex.exec(url.pathname);
|
|
393
|
+
if (match == null) {
|
|
394
|
+
throw new Error(`Invalid Azure URL: ${url.toString()}`);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const [, owner, project, repo] = match;
|
|
398
|
+
return [owner, project, repo];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function parseAzureHttpsUrl(url: string): [owner: string, project: string, repo: string];
|
|
402
|
+
export function parseAzureHttpsUrl(urlObj: URL): [owner: string, project: string, repo: string];
|
|
403
|
+
export function parseAzureHttpsUrl(arg: URL | string): [owner: string, project: string, repo: string] {
|
|
404
|
+
const url = typeof arg === 'string' ? new URL(arg) : arg;
|
|
405
|
+
if (vstsHostnameRegex.test(url.hostname)) {
|
|
406
|
+
return parseVstsHttpsUrl(url);
|
|
407
|
+
}
|
|
408
|
+
return parseAzureNewStyleUrl(url);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export function getAzurePullRequestWebUrl(pr: AzurePullRequest): string {
|
|
412
|
+
const url = new URL(pr.url);
|
|
413
|
+
const baseUrl = new URL(url.origin).toString();
|
|
414
|
+
const repoPath = getAzureRepo(pr);
|
|
415
|
+
const isVSTS = vstsHostnameRegex.test(url.hostname);
|
|
416
|
+
if (isVSTS) {
|
|
417
|
+
return `${baseUrl}/${repoPath}/pullrequest/${pr.pullRequestId}`;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const owner = getAzureDevOpsOwner(url);
|
|
421
|
+
return `${baseUrl}/${owner}/${repoPath}/pullrequest/${pr.pullRequestId}`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function fromAzurePullRequestMergeStatusToMergeableState(
|
|
425
|
+
mergeStatus: AzurePullRequestAsyncStatus,
|
|
426
|
+
): PullRequestMergeableState {
|
|
427
|
+
switch (mergeStatus) {
|
|
428
|
+
case 'conflicts':
|
|
429
|
+
return PullRequestMergeableState.Conflicting;
|
|
430
|
+
case 'failure':
|
|
431
|
+
return PullRequestMergeableState.FailingChecks;
|
|
432
|
+
case 'rejectedByPolicy':
|
|
433
|
+
return PullRequestMergeableState.BlockedByPolicy;
|
|
434
|
+
case 'succeeded':
|
|
435
|
+
return PullRequestMergeableState.Mergeable;
|
|
436
|
+
case 'notSet':
|
|
437
|
+
case 'queued':
|
|
438
|
+
default:
|
|
439
|
+
return PullRequestMergeableState.Unknown;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export function fromAzurePullRequestVoteToReviewState(vote: AzurePullRequestVote): PullRequestReviewState {
|
|
444
|
+
switch (vote) {
|
|
445
|
+
case 10:
|
|
446
|
+
case 5:
|
|
447
|
+
return PullRequestReviewState.Approved;
|
|
448
|
+
case 0:
|
|
449
|
+
return PullRequestReviewState.ReviewRequested;
|
|
450
|
+
case -5:
|
|
451
|
+
case -10:
|
|
452
|
+
return PullRequestReviewState.ChangesRequested;
|
|
453
|
+
default:
|
|
454
|
+
return PullRequestReviewState.ReviewRequested;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export function fromAzureUserWithVoteToReviewer(reviewer: AzureUserWithVote): PullRequestReviewer {
|
|
459
|
+
return {
|
|
460
|
+
isCodeOwner: undefined,
|
|
461
|
+
reviewer: {
|
|
462
|
+
avatarUrl: reviewer.imageUrl,
|
|
463
|
+
id: reviewer.id,
|
|
464
|
+
name: reviewer.displayName,
|
|
465
|
+
url: reviewer.url,
|
|
466
|
+
},
|
|
467
|
+
state: fromAzurePullRequestVoteToReviewState(reviewer.vote ?? 0),
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export function getAzurePullRequestReviewDecision(
|
|
472
|
+
votes: AzurePullRequestVote[],
|
|
473
|
+
): PullRequestReviewDecision | undefined {
|
|
474
|
+
const reviewStates = votes.map(vote => fromAzurePullRequestVoteToReviewState(vote));
|
|
475
|
+
if (reviewStates.includes(PullRequestReviewState.ChangesRequested)) {
|
|
476
|
+
return PullRequestReviewDecision.ChangesRequested;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (reviewStates.includes(PullRequestReviewState.ReviewRequested)) {
|
|
480
|
+
return PullRequestReviewDecision.ReviewRequired;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (reviewStates.includes(PullRequestReviewState.Approved)) {
|
|
484
|
+
return PullRequestReviewDecision.Approved;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return undefined;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export function fromAzureReviewerToPullRequestMember(reviewer: AzureUser): PullRequestMember {
|
|
491
|
+
return {
|
|
492
|
+
avatarUrl: reviewer.imageUrl,
|
|
493
|
+
id: reviewer.id,
|
|
494
|
+
name: reviewer.displayName,
|
|
495
|
+
url: reviewer.url,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function normalizeAzureBranchName(branchName: string): string {
|
|
500
|
+
return branchName.startsWith('refs/heads/') ? branchName.replace('refs/heads/', '') : branchName;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function fromAzureUserToMember(user: AzureUser, type: 'issue'): IssueMember;
|
|
504
|
+
function fromAzureUserToMember(user: AzureUser, type: 'pullRequest'): PullRequestMember;
|
|
505
|
+
function fromAzureUserToMember(user: AzureUser, _type: 'issue' | 'pullRequest'): PullRequestMember | IssueMember {
|
|
506
|
+
return {
|
|
507
|
+
avatarUrl: user.imageUrl,
|
|
508
|
+
id: user.id,
|
|
509
|
+
name: user.displayName,
|
|
510
|
+
url: user.url,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export function fromAzurePullRequest(pr: AzurePullRequest, provider: Provider, orgName: string): PullRequest {
|
|
515
|
+
const url = new URL(pr.url);
|
|
516
|
+
return new PullRequest(
|
|
517
|
+
provider,
|
|
518
|
+
fromAzureUserToMember(pr.createdBy, 'pullRequest'),
|
|
519
|
+
pr.pullRequestId.toString(),
|
|
520
|
+
pr.pullRequestId.toString(),
|
|
521
|
+
pr.title,
|
|
522
|
+
getAzurePullRequestWebUrl(pr),
|
|
523
|
+
{
|
|
524
|
+
owner: getAzureOwner(url),
|
|
525
|
+
repo: pr.repository.name,
|
|
526
|
+
id: pr.repository.id,
|
|
527
|
+
// TODO: Remove this assumption once actual access level is available
|
|
528
|
+
accessLevel: RepositoryAccessLevel.Write,
|
|
529
|
+
},
|
|
530
|
+
azurePullRequestStatusToState(pr.status),
|
|
531
|
+
new Date(pr.creationDate),
|
|
532
|
+
new Date(pr.closedDate || pr.creationDate),
|
|
533
|
+
pr.closedDate ? new Date(pr.closedDate) : undefined,
|
|
534
|
+
pr.closedDate && pr.status === 'completed' ? new Date(pr.closedDate) : undefined,
|
|
535
|
+
fromAzurePullRequestMergeStatusToMergeableState(pr.mergeStatus ?? 'notSet'),
|
|
536
|
+
undefined,
|
|
537
|
+
{
|
|
538
|
+
base: {
|
|
539
|
+
branch: pr.targetRefName ? normalizeAzureBranchName(pr.targetRefName) : '',
|
|
540
|
+
sha: pr.lastMergeTargetCommit?.commitId ?? '',
|
|
541
|
+
repo: pr.repository.name,
|
|
542
|
+
owner: getAzureOwner(url),
|
|
543
|
+
exists: pr.targetRefName != null,
|
|
544
|
+
url: pr.repository.webUrl,
|
|
545
|
+
},
|
|
546
|
+
head: {
|
|
547
|
+
branch: pr.sourceRefName ? normalizeAzureBranchName(pr.sourceRefName) : '',
|
|
548
|
+
sha: pr.lastMergeSourceCommit?.commitId ?? '',
|
|
549
|
+
repo: pr.forkSource?.repository != null ? pr.forkSource.repository.name : pr.repository.name,
|
|
550
|
+
owner: getAzureOwner(url),
|
|
551
|
+
exists: pr.sourceRefName != null,
|
|
552
|
+
url: pr.forkSource?.repository != null ? pr.forkSource.repository.webUrl : pr.repository.webUrl,
|
|
553
|
+
},
|
|
554
|
+
isCrossRepository: pr.forkSource != null,
|
|
555
|
+
},
|
|
556
|
+
pr.isDraft,
|
|
557
|
+
undefined,
|
|
558
|
+
undefined,
|
|
559
|
+
undefined,
|
|
560
|
+
undefined,
|
|
561
|
+
getAzurePullRequestReviewDecision(pr.reviewers?.filter(r => r.isRequired).map(r => r.vote ?? 0) ?? []),
|
|
562
|
+
pr.reviewers.filter(r => r.vote == null || r.vote === 0).map(r => fromAzureUserWithVoteToReviewer(r)),
|
|
563
|
+
pr.reviewers.filter(r => r.vote != null && r.vote !== 0).map(r => fromAzureUserWithVoteToReviewer(r)),
|
|
564
|
+
pr.reviewers.map(r => fromAzureReviewerToPullRequestMember(r)),
|
|
565
|
+
undefined,
|
|
566
|
+
{
|
|
567
|
+
id: pr.repository?.project?.id,
|
|
568
|
+
name: pr.repository.project.name,
|
|
569
|
+
resourceId: '', // TODO: This is a workaround until we can get the org id here.
|
|
570
|
+
resourceName: orgName,
|
|
571
|
+
},
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
export function fromAzureWorkItem(
|
|
576
|
+
workItem: WorkItem,
|
|
577
|
+
provider: Provider,
|
|
578
|
+
project: AzureProjectDescriptor,
|
|
579
|
+
stateCategory?: AzureWorkItemStateCategory,
|
|
580
|
+
): Issue {
|
|
581
|
+
return new Issue(
|
|
582
|
+
provider,
|
|
583
|
+
workItem.id.toString(),
|
|
584
|
+
workItem.id.toString(),
|
|
585
|
+
workItem.fields['System.Title'],
|
|
586
|
+
workItem._links.html.href,
|
|
587
|
+
new Date(workItem.fields['System.CreatedDate']),
|
|
588
|
+
new Date(workItem.fields['System.ChangedDate']),
|
|
589
|
+
isClosedAzureWorkItemStateCategory(stateCategory),
|
|
590
|
+
azureWorkItemsStateCategoryToState(stateCategory),
|
|
591
|
+
fromAzureUserToMember(workItem.fields['System.CreatedBy'], 'issue'),
|
|
592
|
+
workItem.fields['System.AssignedTo'] != null
|
|
593
|
+
? [fromAzureUserToMember(workItem.fields['System.AssignedTo'], 'issue')]
|
|
594
|
+
: [],
|
|
595
|
+
undefined,
|
|
596
|
+
workItem.fields['Microsoft.VSTS.Common.ClosedDate']
|
|
597
|
+
? new Date(workItem.fields['Microsoft.VSTS.Common.ClosedDate'])
|
|
598
|
+
: undefined,
|
|
599
|
+
undefined,
|
|
600
|
+
workItem.fields['System.CommentCount'],
|
|
601
|
+
undefined,
|
|
602
|
+
workItem.fields['System.Description'],
|
|
603
|
+
project,
|
|
604
|
+
);
|
|
605
|
+
}
|