@gitkraken/core-gitlens 0.18.0 → 0.19.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/dist/git/models/issue.d.ts +13 -3
  3. package/dist/git/models/issue.js +3 -1
  4. package/dist/git/models/issue.js.map +1 -1
  5. package/dist/git/models/pullRequest.d.ts +13 -2
  6. package/dist/git/models/pullRequest.js +3 -1
  7. package/dist/git/models/pullRequest.js.map +1 -1
  8. package/dist/git/utils/issue.utils.js +2 -1
  9. package/dist/git/utils/issue.utils.js.map +1 -1
  10. package/dist/git/utils/pullRequest.utils.js +1 -0
  11. package/dist/git/utils/pullRequest.utils.js.map +1 -1
  12. package/dist/plus/integrations/models/gitHostIntegration.d.ts +7 -6
  13. package/dist/plus/integrations/models/gitHostIntegration.js +4 -3
  14. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  15. package/dist/plus/integrations/models/integration.d.ts +5 -0
  16. package/dist/plus/integrations/models/integration.js.map +1 -1
  17. package/dist/plus/integrations/providers/azure/azure.d.ts +8 -6
  18. package/dist/plus/integrations/providers/azure/azure.js +3 -2
  19. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  20. package/dist/plus/integrations/providers/azure/models.d.ts +4 -2
  21. package/dist/plus/integrations/providers/azure/models.js +6 -5
  22. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  23. package/dist/plus/integrations/providers/azureDevOps.d.ts +15 -13
  24. package/dist/plus/integrations/providers/azureDevOps.js +26 -22
  25. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  26. package/dist/plus/integrations/providers/azureDevOpsServer.js +6 -3
  27. package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
  28. package/dist/plus/integrations/providers/issueSorts.d.ts +1 -0
  29. package/dist/plus/integrations/providers/models.d.ts +26 -4
  30. package/dist/plus/integrations/providers/models.js +27 -5
  31. package/dist/plus/integrations/providers/models.js.map +1 -1
  32. package/dist/plus/integrations/reads/drains.d.ts +2 -1
  33. package/dist/plus/integrations/reads/drains.js.map +1 -1
  34. package/dist/plus/integrations/reads/etag.js +3 -1
  35. package/dist/plus/integrations/reads/etag.js.map +1 -1
  36. package/docs/integrations.md +21 -2
  37. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"models.js","names":["Issue","GitPullRequestState","GitPullRequestMergeableState","GitPullRequestReviewState"],"sources":["../../../../../../plus/integrations/src/providers/azure/models.ts"],"sourcesContent":["import type { AzureWorkItemHtmlField } from '@gitkraken/provider-apis';\nimport { getAzureWorkItemDescription } from '@gitkraken/provider-apis';\nimport type { IssueIteration, IssueMember, IssueProjection, IssueProviderState } from '@gitlens/git/models/issue.js';\nimport { Issue } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequestState } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type { PullRequestMember, PullRequestProjection, PullRequestReviewer } from '@gitlens/git/models/pullRequest.js';\nimport {\n\tPullRequest,\n\tPullRequestMergeableState,\n\tPullRequestReviewDecision,\n\tPullRequestReviewState,\n} from '@gitlens/git/models/pullRequest.js';\nimport type { Provider } from '@gitlens/git/models/remoteProvider.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { isTerminalIssueStateCategory } from '@gitlens/git/utils/issue.utils.js';\nimport type { IssueEtagFields, PullRequestEtagFields, PullRequestEtagInclude } from '../../models/integration.js';\nimport {\n\tazureWorkItemDescriptionRule,\n\tazureWorkItemViewerRule,\n\tpullRequestRevision,\n} from '../../models/integration.js';\nimport type { ProviderAccount, ProviderIssue } from '../models.js';\nimport {\n\tfromProviderPullRequestMergeableState,\n\tfromProviderPullRequestState,\n\tGitPullRequestMergeableState,\n\tGitPullRequestReviewState,\n\tGitPullRequestState,\n\tissueNamesPeople,\n\ttoIssueBodyField,\n} from '../models.js';\nimport type { ProviderPullRequestReview } from '../pullRequestReviews.js';\nimport {\n\tdecideProviderReviewDecision,\n\tfromPullRequestReviewDecision,\n\ttoCompletedReviews,\n\ttoReviewRequests,\n} from '../pullRequestReviews.js';\n\nexport const vstsHostnameSuffix = '.visualstudio.com';\n\nexport interface AzureRepositoryDescriptor extends ResourceDescriptor {\n\towner: string;\n\tname: string;\n\tproject?: string;\n\tvirtualDirectory?: string;\n}\n\nexport interface AzureOrganizationDescriptor extends ResourceDescriptor {\n\tid: string;\n\tname: string;\n}\n\nexport interface AzureProjectDescriptor extends ResourceDescriptor {\n\tid: string;\n\tname: string;\n\tresourceId: string;\n\tresourceName: string;\n}\n\nexport interface AzureRemoteRepositoryDescriptor extends ResourceDescriptor {\n\tid: string;\n\tnodeId?: string;\n\tresourceName: string;\n\tname: string;\n\tprojectName?: string;\n\turl?: string;\n\tcloneUrlHttps?: string;\n\tcloneUrlSsh?: string;\n}\n\nexport interface AzureProjectInputDescriptor extends ResourceDescriptor {\n\towner: string;\n\tname: string;\n}\n\nexport type AzureWorkItemStateCategory = 'Proposed' | 'InProgress' | 'Resolved' | 'Completed' | 'Removed';\n\n/**\n * The workflow category of an Azure DevOps state category, as a work item type's states endpoint reports it:\n * `Removed` is abandoned work, so `CANCELED`, kept apart from the `DONE` of `Completed` and `Resolved`.\n * `undefined` for a category Azure doesn't document, which then neither opens nor closes the work item.\n */\nexport function azureIssueStateCategory(\n\tcategory: AzureWorkItemStateCategory | undefined,\n): IssueProviderState['category'] {\n\tswitch (category) {\n\t\tcase 'Removed':\n\t\t\treturn 'CANCELED';\n\t\tcase 'Completed':\n\t\tcase 'Resolved':\n\t\t\treturn 'DONE';\n\t\tcase 'InProgress':\n\t\t\treturn 'IN_PROGRESS';\n\t\tcase 'Proposed':\n\t\t\treturn 'TO_DO';\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nexport function isClosedAzureWorkItemStateCategory(category: AzureWorkItemStateCategory | undefined): boolean {\n\treturn isTerminalIssueStateCategory(azureIssueStateCategory(category));\n}\n\nexport function azureWorkItemsStateCategoryToState(\n\tcategory: AzureWorkItemStateCategory | undefined,\n\tclosedDate?: Date,\n): IssueOrPullRequestState | undefined {\n\tif (azureIssueStateCategory(category) != null) {\n\t\treturn isClosedAzureWorkItemStateCategory(category) ? 'closed' : 'opened';\n\t}\n\n\treturn closedDate != null ? 'closed' : undefined;\n}\n\nexport interface AzureLink {\n\thref: string;\n}\n\nexport interface AzureUser {\n\tdisplayName: string;\n\turl: string;\n\t_links: {\n\t\tavatar: AzureLink;\n\t};\n\tid: string;\n\tuniqueName: string;\n\timageUrl: string;\n\tdescriptor?: string;\n}\n\nexport interface AzureUserWithVote extends AzureUser {\n\tisFlagged?: boolean;\n\thasDeclined?: boolean;\n\tisReapprove?: boolean;\n\tisRequired?: boolean;\n\tvote?: AzurePullRequestVote;\n}\n\nexport type AzurePullRequestVote =\n\t| 10 // approved\n\t| 5 // approved with suggestions\n\t| 0 // no vote\n\t| -5 // waiting for author\n\t| -10; // rejected\n\nexport interface AzureWorkItemCommentVersionRef {\n\tcommentId: number;\n\tcreatedInRevision: number;\n\tisDeleted: boolean;\n\ttext: string;\n\turl: string;\n\tversion: number;\n}\n\nexport interface AzureWorkItemRelation {\n\tattributes: {\n\t\t[key: string]: string;\n\t};\n\trelation: string;\n\turl: string;\n}\n\nexport interface WorkItem {\n\t_links: {\n\t\tfields: AzureLink;\n\t\thtml: AzureLink;\n\t\tself: AzureLink;\n\t\tworkItemComments: AzureLink;\n\t\tworkItemRevisions: AzureLink;\n\t\tworkItemType: AzureLink;\n\t\tworkItemUpdates: AzureLink;\n\t};\n\tfields: {\n\t\t//'System.AreaPath': string;\n\t\t'System.TeamProject': string;\n\t\t'System.IterationPath'?: string;\n\t\t'System.WorkItemType': string;\n\t\t'System.State': string;\n\t\t// 'System.Reason': string;\n\t\t'System.AssignedTo': AzureUser | string;\n\t\t'System.CreatedDate': string;\n\t\t'System.CreatedBy': AzureUser | string;\n\t\t'System.ChangedDate': string;\n\t\t'System.ChangedBy': AzureUser;\n\t\t'System.CommentCount': number;\n\t\t'System.Description': string;\n\t\t'System.Title': string;\n\t\t'Microsoft.VSTS.Common.ClosedDate': string;\n\t\t// 'Microsoft.VSTS.Common.StateChangeDate': string;\n\t\t// 'Microsoft.VSTS.Common.Priority': number;\n\t\t// 'Microsoft.VSTS.Common.Severity': string;\n\t\t// 'Microsoft.VSTS.Common.ValueArea': string;\n\t};\n\tid: number;\n\trev: number;\n\turl: string;\n\tcommentVersionRef?: AzureWorkItemCommentVersionRef;\n\trelations?: AzureWorkItemRelation[];\n}\n\nexport interface AzureWorkItemState {\n\tname: string;\n\tcolor: string;\n\tcategory: AzureWorkItemStateCategory;\n}\n\nexport type AzurePullRequestStatus = 'abandoned' | 'active' | 'completed' | 'notSet';\nexport function azurePullRequestStatusToState(status: AzurePullRequestStatus): IssueOrPullRequestState {\n\tswitch (status) {\n\t\tcase 'abandoned':\n\t\t\treturn 'closed';\n\t\tcase 'completed':\n\t\t\treturn 'merged';\n\t\tcase 'active':\n\t\tcase 'notSet':\n\t\tdefault:\n\t\t\treturn 'opened';\n\t}\n}\nexport function isClosedAzurePullRequestStatus(status: AzurePullRequestStatus): boolean {\n\treturn azurePullRequestStatusToState(status) !== 'opened';\n}\n\nexport type AzureProjectState = 'createPending' | 'deleted' | 'deleting' | 'new' | 'unchanged' | 'wellFormed';\nexport type AzureProjectVisibility = 'private' | 'public';\n\nexport interface AzureProject {\n\tid: string;\n\tname: string;\n\turl: string;\n\tstate: AzureProjectState;\n\trevision: number;\n\tvisibility: AzureProjectVisibility;\n\tlastUpdateTime: string;\n}\n\nexport interface AzureRepositoryReference {\n\tid: string;\n\tname: string;\n\turl: string;\n\tremoteUrl?: string;\n}\n\nexport interface AzureRepository extends AzureRepositoryReference {\n\tproject: AzureProject;\n\tsize: number;\n\tremoteUrl: string;\n\tsshUrl: string;\n\twebUrl: string;\n\tisDisabled: boolean;\n\tisInMaintenance: boolean;\n}\n\nexport interface AzurePullRequestRepository extends AzureRepositoryReference {\n\tproject: Pick<AzureProject, 'id' | 'name'>;\n}\n\n/** The URL fields of a repository response — all the fork lookup reads, and all an older `api-version` promises. */\nexport type AzureRepositoryUrls = Partial<Pick<AzureRepository, 'webUrl' | 'remoteUrl'>>;\n\n/** The `GET .../_apis/git/repositories/{repositoryId or name}` response, adding fork/default-branch fields to {@link AzureRepository}. */\nexport interface AzureRepositoryWithMetadata extends AzureRepository {\n\t/** Fully-qualified ref, e.g. `refs/heads/main`. */\n\tdefaultBranch?: string;\n\tisFork?: boolean;\n\tparentRepository?: {\n\t\tid: string;\n\t\tname?: string;\n\t\tproject?: { id: string; name: string };\n\t};\n}\n\nexport interface AzureGitUser {\n\tdate?: string;\n\temail?: string;\n\timageUrl?: string;\n\tname: string;\n}\n\nexport interface AzureGitCommitRef {\n\tcommitId: string;\n\turl: string;\n}\n\nexport interface AzureGitCommit {\n\t_links: {\n\t\tchanges: AzureLink;\n\t\trepository: AzureLink;\n\t\tself: AzureLink;\n\t\tweb: AzureLink;\n\t};\n\tauthor: AzureGitUser;\n\tcomment: string;\n\tcommentTruncated?: boolean;\n\tcommitId: string;\n\tcommitTooManyChanges?: boolean;\n\tcommitter: AzureGitUser;\n\tparents: string[];\n\tpush: {\n\t\tdate: string;\n\t\tpushedBy: AzureUser;\n\t\tpushId: number;\n\t};\n\tremoteUrl: string;\n\tstatuses?: AzureGitStatus[];\n\ttreeId: string;\n\turl: string;\n\tworkItems?: AzureResourceRef[];\n}\n\nexport interface AzureResourceRef {\n\tid: string;\n\turl: string;\n}\n\nexport interface AzurePullRequestCompletionOptions {\n\tautoCompleteIgnoreConflicts: number[];\n\tbypassPolicy: boolean;\n\tbypassReason: string;\n\tdeleteSourceBranch: boolean;\n\tmergeCommitMessage: string;\n\tmergeStrategy: 'noFastForward' | 'rebase' | 'rebaseMerge' | 'squash';\n\tsquashMerge: boolean;\n\ttransitionWorkItems: boolean;\n\ttriggeredByAutoComplete: boolean;\n}\n\nexport interface AzureGitStatus {\n\tcontext: {\n\t\tname: string;\n\t\tgenre: string;\n\t};\n\tcreatedBy: AzureUser;\n\tcreateDate: string;\n\tdescription: string;\n\tstate: 'error' | 'failed' | 'notApplicable' | 'notSet' | 'pending' | 'succeeded';\n\ttargetUrl: string;\n\tupdateDate: string;\n}\n\nexport interface AzureGitForkRef {\n\tcreator: AzureUser;\n\tisLocked: boolean;\n\tisLockedBy: AzureUser;\n\tname: string;\n\tobjectId: string;\n\tpeeledObjectId: string;\n\trepository: AzureRepositoryReference;\n\tstatuses: AzureGitStatus[];\n\turl: string;\n}\n\nexport interface AzureWebApiTagDefinition {\n\tactive: boolean;\n\tid: string;\n\tname: string;\n\turl: string;\n}\n\nexport interface AzureGitPullRequestMergeOptions {\n\tconflictAuthorshipCommits: boolean;\n\tdetectRenameFalsePositives: boolean;\n\tdisableRenames: boolean;\n}\n\nexport type AzurePullRequestAsyncStatus =\n\t| 'conflicts'\n\t| 'failure'\n\t| 'notSet'\n\t| 'queued'\n\t| 'rejectedByPolicy'\n\t| 'succeeded';\n\nexport interface AzurePullRequest {\n\trepository: AzurePullRequestRepository;\n\tpullRequestId: number;\n\tcodeReviewId: number;\n\tstatus: AzurePullRequestStatus;\n\tcreatedBy: AzureUser;\n\tcreationDate: string;\n\tclosedDate?: string;\n\tclosedBy?: AzureUser; // Can be missed even if closedDate is presented.\n\ttitle: string;\n\tdescription: string;\n\tforkSource?: AzureGitForkRef;\n\tsourceRefName: string;\n\ttargetRefName: string;\n\tisDraft: boolean;\n\tmergeId: string;\n\tmergeStatus?: AzurePullRequestAsyncStatus;\n\tlastMergeCommit?: AzureGitCommitRef;\n\tlastMergeSourceCommit: AzureGitCommitRef;\n\tlastMergeTargetCommit: AzureGitCommitRef;\n\treviewers: AzureUserWithVote[];\n\turl: string;\n\tsupportsIterations: boolean;\n}\n\nexport interface AzurePullRequestWithLinks extends AzurePullRequest {\n\t_links: {\n\t\tself: AzureLink;\n\t\trepository: AzureLink;\n\t\tworkItems: AzureLink;\n\t\tsourceBranch: AzureLink;\n\t\ttargetBranch: AzureLink;\n\t\tstatuses: AzureLink;\n\t\tsourceCommit: AzureLink;\n\t\ttargetCommit: AzureLink;\n\t\tcreatedBy: AzureLink;\n\t\titerations: AzureLink;\n\t};\n\tartifactId: string;\n\tautoCompleteSetBy?: AzureUser;\n\tcommits?: AzureGitCommitRef[];\n\tcompletionOptions?: AzurePullRequestCompletionOptions;\n\tcompletionQueueTime?: string;\n\thasMultipleMergeBases?: boolean;\n\tlabels?: AzureWebApiTagDefinition[];\n\tmergeFailureMessage?: string;\n\tmergeFailureType?: 'caseSensitive' | 'none' | 'objectTooLarge' | 'unknown';\n\tmergeOptions?: AzureGitPullRequestMergeOptions;\n\tremoteUrl?: string;\n\tworkItemRefs?: AzureResourceRef[];\n}\n\nexport function getAzureDevOpsOwner(url: URL): string {\n\treturn url.pathname.split('/')[1];\n}\nexport function isVsts(domain: string): boolean {\n\treturn domain.toLowerCase().endsWith(vstsHostnameSuffix);\n}\n\n/**\n * Encodes one route segment of an Azure DevOps request (#5878): `encodeURIComponent` leaves a dot segment as is, and\n * the URL would resolve it away, re-scoping the request instead of failing it.\n */\nexport function encodeAzurePathSegment(value: string): string {\n\tif (value === '.' || value === '..') throw new Error(`Invalid Azure path segment '${value}'.`);\n\n\treturn encodeURIComponent(value);\n}\n\n/**\n * `baseUrl` and `owner` are the authoritative prefix used for the API request. The payload URL cannot supply that\n * prefix safely because it may address the repository by id or name an untrusted host.\n */\nfunction getAzureRepositoryWebUrl(baseUrl: string, owner: string, projectName: string, repoName: string): string {\n\tconst repoPath = `${encodeURIComponent(owner)}/${encodeURIComponent(projectName)}/_git/${encodeURIComponent(repoName)}`;\n\treturn `${baseUrl.replace(/\\/+$/, '')}/${repoPath}`;\n}\n\nexport function getAzurePullRequestWebUrl(pr: AzurePullRequest, baseUrl: string, owner: string): string {\n\tconst repoUrl = getAzureRepositoryWebUrl(baseUrl, owner, pr.repository.project.name, pr.repository.name);\n\treturn `${repoUrl}/pullrequest/${pr.pullRequestId}`;\n}\n\n/**\n * Whether `url` and `expected` are the two cloud spellings of one organization: a cloud organization answers on both\n * `dev.azure.com/{owner}` and the legacy `{owner}.visualstudio.com`, so those name the same host. Anything else is\n * somewhere we didn't ask.\n */\nfunction isSameAzureCloudOrganization(url: URL, expected: URL, expectedOwner: string): boolean {\n\tif (url.protocol !== expected.protocol) return false;\n\n\tconst owner = expectedOwner.toLowerCase();\n\tif (expected.hostname !== 'dev.azure.com' && !isVsts(expected.hostname)) return false;\n\n\tif (url.hostname === 'dev.azure.com') return getAzureDevOpsOwner(url).toLowerCase() === owner;\n\n\treturn url.hostname === `${owner}${vstsHostnameSuffix}`;\n}\n\n/**\n * Whether `url` sits under the collection the integration is configured to talk to — `{expectedUrl}/{expectedOwner}`.\n * A matching origin says nothing on its own: Azure organizations and self-hosted collections are path segments.\n */\nfunction isUnderAzureCollection(url: URL, expected: URL, expectedOwner: string): boolean {\n\tconst base = expected.pathname.replace(/\\/+$/, '');\n\tconst prefix = `${base}/${encodeURIComponent(expectedOwner)}/`.toLowerCase();\n\treturn `${url.pathname}/`.toLowerCase().startsWith(prefix);\n}\n\n/**\n * Restricts a provider-supplied repository URL to the integration's collection and removes credentials, query, and\n * fragment before a consumer can use it as a git remote. Takes the value as the payloads carry it — every one of\n * these URLs is optional — so a caller never has to spell the absent case itself.\n */\nexport function sanitizeAzureRepositoryUrl(\n\tvalue: string | undefined,\n\texpectedUrl: string,\n\texpectedOwner: string,\n): string | undefined {\n\tif (value == null) return undefined;\n\n\tlet expected: URL;\n\tlet url: URL;\n\ttry {\n\t\texpected = new URL(expectedUrl);\n\t\turl = new URL(value);\n\t} catch {\n\t\treturn undefined;\n\t}\n\n\tif (expected.protocol !== 'https:' && expected.protocol !== 'http:') return undefined;\n\tif (!expectedOwner) return undefined;\n\n\tif (url.origin === expected.origin) {\n\t\tif (!isUnderAzureCollection(url, expected, expectedOwner)) return undefined;\n\t} else if (!isSameAzureCloudOrganization(url, expected, expectedOwner)) {\n\t\treturn undefined;\n\t}\n\n\turl.username = '';\n\turl.password = '';\n\turl.search = '';\n\turl.hash = '';\n\treturn url.toString();\n}\n\n/**\n * The clone URL the payload carries for a repository, restricted to the integration's collection and then\n * cross-checked against the repository that payload names.\n *\n * The collection check alone is not enough for a URL that is READ rather than built. Every other repository URL the\n * model reports is built from the authoritative prefix, so the payload cannot move it; this one cannot be built,\n * because Azure spells a repository for git the same way it spells it for the web only by convention. A URL under\n * the right collection may still name a DIFFERENT repository in it, which would leave `cloneHttps` describing one\n * repository while `url` describes another.\n *\n * Azure ends a clone URL with `_git/{repo}` on every host style — including the short `{owner}/_git/{repo}` form it\n * uses when a repository carries its project's name, and the legacy `{owner}.visualstudio.com` spelling that has no\n * organization segment at all — so the repository name is the one part comparable across all of them. A same-named\n * repository in another project of the same organization is the single substitution this cannot catch.\n */\nfunction getAzureRepositoryCloneUrl(\n\trepository: AzureRepositoryReference,\n\tbaseUrl: string,\n\towner: string,\n): string | undefined {\n\tconst sanitized = sanitizeAzureRepositoryUrl(repository.remoteUrl, baseUrl, owner);\n\tif (sanitized == null) return undefined;\n\n\t// Safe to parse: `sanitizeAzureRepositoryUrl` returns what it already parsed.\n\tconst segments = new URL(sanitized).pathname.split('/').filter(s => s.length > 0);\n\tconst name = segments.pop();\n\tif (name == null || segments.pop() !== '_git') return undefined;\n\n\tlet decoded: string;\n\ttry {\n\t\tdecoded = decodeURIComponent(name);\n\t} catch {\n\t\treturn undefined;\n\t}\n\n\t// Azure resolves project and repository names case-insensitively, so comparing them any other way would refuse a\n\t// URL the provider considers the same one.\n\treturn decoded.toLowerCase() === repository.name.toLowerCase() ? sanitized : undefined;\n}\n\nexport function fromAzurePullRequestMergeStatusToMergeableState(\n\tmergeStatus: AzurePullRequestAsyncStatus,\n): PullRequestMergeableState {\n\tswitch (mergeStatus) {\n\t\tcase 'conflicts':\n\t\t\treturn PullRequestMergeableState.Conflicting;\n\t\tcase 'failure':\n\t\t\treturn PullRequestMergeableState.FailingChecks;\n\t\tcase 'rejectedByPolicy':\n\t\t\treturn PullRequestMergeableState.BlockedByPolicy;\n\t\tcase 'succeeded':\n\t\t\treturn PullRequestMergeableState.Mergeable;\n\t\tcase 'notSet':\n\t\tcase 'queued':\n\t\tdefault:\n\t\t\treturn PullRequestMergeableState.Unknown;\n\t}\n}\n\nexport function fromAzurePullRequestVoteToReviewState(vote: AzurePullRequestVote): PullRequestReviewState {\n\tswitch (vote) {\n\t\tcase 10:\n\t\tcase 5:\n\t\t\treturn PullRequestReviewState.Approved;\n\t\tcase 0:\n\t\t\treturn PullRequestReviewState.ReviewRequested;\n\t\tcase -5:\n\t\tcase -10:\n\t\t\treturn PullRequestReviewState.ChangesRequested;\n\t\tdefault:\n\t\t\treturn PullRequestReviewState.ReviewRequested;\n\t}\n}\n\nexport function fromAzureUserWithVoteToReviewer(reviewer: AzureUserWithVote): PullRequestReviewer {\n\treturn {\n\t\tisCodeOwner: undefined,\n\t\treviewer: {\n\t\t\tavatarUrl: reviewer.imageUrl,\n\t\t\tid: reviewer.id,\n\t\t\tname: reviewer.displayName,\n\t\t\tusername: reviewer.uniqueName,\n\t\t\turl: reviewer.url,\n\t\t},\n\t\tstate: fromAzurePullRequestVoteToReviewState(reviewer.vote ?? 0),\n\t};\n}\n\nexport function getAzurePullRequestReviewDecision(\n\tvotes: AzurePullRequestVote[],\n): PullRequestReviewDecision | undefined {\n\tconst reviewStates = votes.map(vote => fromAzurePullRequestVoteToReviewState(vote));\n\tif (reviewStates.includes(PullRequestReviewState.ChangesRequested)) {\n\t\treturn PullRequestReviewDecision.ChangesRequested;\n\t}\n\n\tif (reviewStates.includes(PullRequestReviewState.ReviewRequested)) {\n\t\treturn PullRequestReviewDecision.ReviewRequired;\n\t}\n\n\tif (reviewStates.includes(PullRequestReviewState.Approved)) {\n\t\treturn PullRequestReviewDecision.Approved;\n\t}\n\n\treturn undefined;\n}\n\nexport function fromAzureReviewerToPullRequestMember(reviewer: AzureUser): PullRequestMember {\n\treturn {\n\t\tavatarUrl: reviewer.imageUrl,\n\t\tid: reviewer.id,\n\t\tname: reviewer.displayName,\n\t\tusername: reviewer.uniqueName,\n\t\turl: reviewer.url,\n\t};\n}\n\nexport function normalizeAzureBranchName(branchName: string): string {\n\treturn branchName.startsWith('refs/heads/') ? branchName.replace('refs/heads/', '') : branchName;\n}\n\nfunction fromAzureUserToMember(user: AzureUser, type: 'issue'): IssueMember;\nfunction fromAzureUserToMember(user: AzureUser, type: 'pullRequest'): PullRequestMember;\nfunction fromAzureUserToMember(user: AzureUser, _type: 'issue' | 'pullRequest'): PullRequestMember | IssueMember {\n\treturn {\n\t\tavatarUrl: user.imageUrl,\n\t\tid: user.id,\n\t\tname: user.displayName,\n\t\tusername: user.uniqueName,\n\t\turl: user.url,\n\t};\n}\n\n/**\n * The URLs of a cross-repository pull request's fork, resolved by a lookup because the pull request payload embeds\n * only an abbreviated reference to it: `url` is the URL the head ref reports — the fork's web URL, or its clone URL\n * when the response carries no web URL — and `cloneHttps` its HTTPS clone URL.\n *\n * A fork that resolves neither is reported as no fork at all rather than as an object with nothing in it, so `url`\n * is always present here; `cloneHttps` alone is best-effort, and is absent when the response omits it or its URL\n * sits outside the integration's collection.\n */\nexport interface AzureForkRepositoryUrls {\n\turl: string;\n\tcloneHttps: string | undefined;\n}\n\n/**\n * `baseUrl` and `owner` are the prefix the pull request was read through; every URL the model reports is built from\n * them. The required `forkRepositoryUrls` argument makes each call site resolve the best-effort fork URLs explicitly.\n */\nexport function fromAzurePullRequest(\n\tpr: AzurePullRequest,\n\tprovider: Provider,\n\towner: string,\n\tbaseUrl: string,\n\tforkRepositoryUrls: AzureForkRepositoryUrls | undefined,\n\tprojection?: PullRequestProjection,\n): PullRequest {\n\tconst baseRepositoryUrl = getAzureRepositoryWebUrl(baseUrl, owner, pr.repository.project.name, pr.repository.name);\n\tconst baseCloneHttps = getAzureRepositoryCloneUrl(pr.repository, baseUrl, owner);\n\n\tconst forkRepository = pr.forkSource?.repository;\n\tconst headRepositoryUrl = forkRepository == null ? baseRepositoryUrl : forkRepositoryUrls?.url;\n\tconst headCloneHttps = forkRepository == null ? baseCloneHttps : forkRepositoryUrls?.cloneHttps;\n\n\treturn new PullRequest(\n\t\tprovider,\n\t\tfromAzureUserToMember(pr.createdBy, 'pullRequest'),\n\t\tpr.pullRequestId.toString(),\n\t\tpr.pullRequestId.toString(),\n\t\tpr.title,\n\t\tgetAzurePullRequestWebUrl(pr, baseUrl, owner),\n\t\t{\n\t\t\towner: owner,\n\t\t\trepo: pr.repository.name,\n\t\t\tid: pr.repository.id,\n\t\t},\n\t\tazurePullRequestStatusToState(pr.status),\n\t\tnew Date(pr.creationDate),\n\t\tnew Date(pr.closedDate || pr.creationDate),\n\t\tpr.closedDate ? new Date(pr.closedDate) : undefined,\n\t\tpr.closedDate && pr.status === 'completed' ? new Date(pr.closedDate) : undefined,\n\t\t// The read selects the merge status, so an absent one is Azure's own \"not set\", as provider-apis maps it.\n\t\tfromAzurePullRequestMergeStatusToMergeableState(pr.mergeStatus ?? 'notSet'),\n\t\tundefined,\n\t\t{\n\t\t\tbase: {\n\t\t\t\tbranch: pr.targetRefName ? normalizeAzureBranchName(pr.targetRefName) : '',\n\t\t\t\tsha: pr.lastMergeTargetCommit?.commitId ?? '',\n\t\t\t\trepo: pr.repository.name,\n\t\t\t\towner: owner,\n\t\t\t\texists: pr.targetRefName != null,\n\t\t\t\turl: baseRepositoryUrl,\n\t\t\t\tcloneHttps: baseCloneHttps,\n\t\t\t},\n\t\t\thead: {\n\t\t\t\tbranch: pr.sourceRefName ? normalizeAzureBranchName(pr.sourceRefName) : '',\n\t\t\t\tsha: pr.lastMergeSourceCommit?.commitId ?? '',\n\t\t\t\trepo: forkRepository?.name ?? pr.repository.name,\n\t\t\t\towner: owner,\n\t\t\t\texists: pr.sourceRefName != null,\n\t\t\t\turl: headRepositoryUrl,\n\t\t\t\tcloneHttps: headCloneHttps,\n\t\t\t},\n\t\t\tisCrossRepository: pr.forkSource != null,\n\t\t},\n\t\tpr.isDraft,\n\t\tundefined,\n\t\tundefined,\n\t\tundefined,\n\t\tundefined,\n\t\tgetAzurePullRequestReviewDecision(pr.reviewers?.filter(r => r.isRequired).map(r => r.vote ?? 0) ?? []),\n\t\tpr.reviewers.filter(r => r.vote == null || r.vote === 0).map(r => fromAzureUserWithVoteToReviewer(r)),\n\t\tpr.reviewers.filter(r => r.vote != null && r.vote !== 0).map(r => fromAzureUserWithVoteToReviewer(r)),\n\t\tpr.reviewers.map(r => fromAzureReviewerToPullRequestMember(r)),\n\t\tundefined,\n\t\t{\n\t\t\tid: pr.repository?.project?.id,\n\t\t\tname: pr.repository.project.name,\n\t\t\tresourceId: '', // TODO: This is a workaround until we can get the org id here.\n\t\t\tresourceName: owner,\n\t\t},\n\t\tundefined, // version\n\t\tundefined, // commitCount\n\t\tundefined, // stack\n\t\tundefined, // filesChanged\n\t\tundefined, // body\n\t\tundefined, // number\n\t\tundefined, // authoredByMe\n\t\tprojection,\n\t);\n}\n\nfunction fromAzureWorkItemUserToMember(user: AzureUser | string | undefined): IssueMember | undefined {\n\tif (user == null || (typeof user === 'string' && !user.trim())) return undefined;\n\n\treturn typeof user === 'string' ? { id: user, name: user, username: user } : fromAzureUserToMember(user, 'issue');\n}\n\nexport function fromAzureWorkItem(\n\tworkItem: WorkItem,\n\tprovider: Provider,\n\tproject: AzureProjectDescriptor,\n\tstateCategory?: AzureWorkItemStateCategory,\n\tprojection?: IssueProjection,\n\thtmlFields?: readonly AzureWorkItemHtmlField[],\n\tviewer?: { id: string },\n): Issue {\n\tconst state = workItem.fields['System.State'];\n\tconst description = getAzureWorkItemDescription(workItem.fields, htmlFields);\n\tconst closedDate = parseAzureWorkItemDate(workItem.fields['Microsoft.VSTS.Common.ClosedDate']) ?? undefined;\n\tconst normalizedState = azureWorkItemsStateCategoryToState(stateCategory, closedDate);\n\tconst author = fromAzureWorkItemUserToMember(workItem.fields['System.CreatedBy']);\n\tconst assignee = fromAzureWorkItemUserToMember(workItem.fields['System.AssignedTo']);\n\tconst assignees = assignee != null ? [assignee] : [];\n\treturn new Issue(\n\t\tprovider,\n\t\tworkItem.id.toString(),\n\t\tworkItem.id.toString(),\n\t\tworkItem.fields['System.Title'],\n\t\tworkItem._links.html.href,\n\t\tnew Date(workItem.fields['System.CreatedDate']),\n\t\tnew Date(workItem.fields['System.ChangedDate']),\n\t\tnormalizedState === 'closed',\n\t\tnormalizedState,\n\t\tauthor,\n\t\tassignees,\n\t\tundefined,\n\t\tclosedDate,\n\t\tundefined,\n\t\tworkItem.fields['System.CommentCount'],\n\t\tundefined,\n\t\tdescription?.value,\n\t\tproject,\n\t\tundefined,\n\t\tworkItem.fields['System.WorkItemType'],\n\t\ttypeof state === 'string' && state\n\t\t\t? { name: state, category: azureIssueStateCategory(stateCategory) }\n\t\t\t: undefined,\n\t\tundefined,\n\t\ttoWorkItemIterations(workItem.fields['System.IterationPath']),\n\t\tprojection,\n\t\tdescription != null ? toIssueBodyField(description.field) : undefined,\n\t\tissueNamesPeople({ author: author, assignees: assignees }) ? viewer : undefined,\n\t);\n}\n\n/**\n * Azure reports the iteration as a backslash-delimited path rooted at the project. The project root is its default\n * for a work item with no sprint, so only a nested path names one; the path is the identity because Azure supplies\n * no iteration id here.\n *\n * Mirrors `normalizeIteration` in provider-apis so the same work item yields the same identity whether it is read\n * here or through the SDK. That contract keeps the path **verbatim** — it is what the iteration is matched back by,\n * so it is deliberately not trimmed — and trims only the display name.\n */\nfunction toWorkItemIterations(path: string | undefined): IssueIteration[] | undefined {\n\tif (!path) return undefined;\n\n\tconst segments = path.split('\\\\');\n\tif (segments.length < 2) return undefined;\n\n\tconst name = segments.at(-1)?.trim();\n\treturn name ? [{ id: path, name: name }] : undefined;\n}\n\n/**\n * A work item as `GET …/_apis/wit/workitems/{id}?$expand=Links` returns it. Loosely typed because\n * {@link fromAzureWorkItemToProviderIssue} checks every field it reads, as provider-apis does.\n */\nexport interface AzureWorkItemResponse {\n\tid?: unknown;\n\tfields?: Record<string, unknown>;\n\t_links?: { html?: { href?: unknown } };\n}\n\n/**\n * A cheap read's work item with the workflow category its state classifies to (see\n * {@link toAzureWorkItemEtagFields}). Beside the work item rather than on it, so the wire type stays what Azure sends.\n */\nexport interface AzureWorkItemEtagRead {\n\treadonly workItem: AzureWorkItemResponse;\n\treadonly category: IssueProviderState['category'];\n}\n\n/**\n * Converts a work item as provider-apis' `getIssuesForAzureProject` converts each row it reads, so a work item read\n * by id reaches `toIssueShape` identical to the list read's row for it. provider-apis exports neither that converter\n * nor a single work item read, so this mirrors it field for field; the batch issue tests compare the two, so a\n * provider-apis change that this misses fails there.\n *\n * `undefined` for a work item the SDK would skip: no positive integer id, no title, or no valid created date.\n * `state.name` is the raw `System.State`, as on every list read here, which passes the SDK no state-name map. Like\n * the SDK's row, the state carries no workflow category: `ProvidersApi` classifies both reads' rows the same way\n * afterwards, from the work item type's states.\n */\nexport function fromAzureWorkItemToProviderIssue(\n\tworkItem: AzureWorkItemResponse,\n\tnamespace: string,\n\tproject: string,\n\thtmlFields?: readonly AzureWorkItemHtmlField[],\n): ProviderIssue | undefined {\n\tconst fields = workItem.fields;\n\tconst id = workItem.id;\n\tconst title = fields?.['System.Title'];\n\tconst createdDate = parseAzureWorkItemDate(fields?.['System.CreatedDate']);\n\tif (\n\t\tfields == null ||\n\t\ttypeof id !== 'number' ||\n\t\t!Number.isSafeInteger(id) ||\n\t\tid <= 0 ||\n\t\ttypeof title !== 'string' ||\n\t\t!title.trim() ||\n\t\tcreatedDate == null\n\t) {\n\t\treturn undefined;\n\t}\n\n\tconst assignee = fromAzureWorkItemIdentity(fields['System.AssignedTo']);\n\tconst commentCount = fields['System.CommentCount'];\n\tconst url = workItem._links?.html?.href;\n\tconst tags = fields['System.Tags'];\n\tconst state = fields['System.State'];\n\tconst type = fields['System.WorkItemType'];\n\treturn {\n\t\tid: id.toString(),\n\t\tnumber: id.toString(),\n\t\ttitle: title,\n\t\tcommentCount: typeof commentCount === 'number' && Number.isFinite(commentCount) ? commentCount : null,\n\t\tauthor: fromAzureWorkItemIdentity(fields['System.CreatedBy']),\n\t\tclosedDate: parseAzureWorkItemDate(fields['Microsoft.VSTS.Common.ClosedDate']),\n\t\tcreatedDate: createdDate,\n\t\tupdatedDate: parseAzureWorkItemDate(fields['System.ChangedDate']),\n\t\turl: typeof url === 'string' ? url : null,\n\t\tassignees: assignee != null ? [assignee] : [],\n\t\t...toAzureWorkItemDescriptionFields(fields, htmlFields),\n\t\tstate: typeof state === 'string' && state ? { name: state, color: null } : null,\n\t\ttype: typeof type === 'string' ? type : null,\n\t\titeration: toAzureWorkItemIteration(fields['System.IterationPath']),\n\t\trepository: null,\n\t\tproject: { namespace: namespace, name: project, resourceId: null, key: null, id: null },\n\t\t// provider-apis fills 0, but work items have no reactions.\n\t\tupvoteCount: null,\n\t\tlabels: (typeof tags === 'string' ? tags.split(';') : []).map(tag => ({\n\t\t\tcolor: null,\n\t\t\tdescription: null,\n\t\t\tid: null,\n\t\t\tname: tag.trim(),\n\t\t})),\n\t};\n}\n\nexport function toAzureWorkItemDescriptionFields(\n\tfields: Readonly<Record<string, unknown>> | null | undefined,\n\thtmlFields?: readonly AzureWorkItemHtmlField[],\n): Pick<ProviderIssue, 'description' | 'descriptionField'> {\n\tconst description = getAzureWorkItemDescription(fields, htmlFields);\n\treturn {\n\t\tdescription: description?.value ?? null,\n\t\t...(description != null ? { descriptionField: description.field } : {}),\n\t};\n}\n\n/** An identity field, as provider-apis maps it: `name` from `uniqueName`, `username` from `displayName`. */\nfunction fromAzureWorkItemIdentity(value: unknown): ProviderAccount | null {\n\tif (typeof value === 'string') {\n\t\tif (!value.trim()) return null;\n\n\t\treturn { id: value, name: value, username: value, email: null, avatarUrl: null, url: null };\n\t}\n\n\tconst identity = value as\n\t\t| { id?: unknown; uniqueName?: unknown; displayName?: unknown; _links?: { avatar?: { href?: unknown } } }\n\t\t| null\n\t\t| undefined;\n\tif (typeof identity?.id !== 'string' || !identity.id.trim()) return null;\n\n\tconst avatarUrl = identity._links?.avatar?.href;\n\treturn {\n\t\tavatarUrl: typeof avatarUrl === 'string' ? avatarUrl : null,\n\t\temail: null,\n\t\tid: identity.id,\n\t\tname: typeof identity.uniqueName === 'string' ? identity.uniqueName : null,\n\t\tusername: typeof identity.displayName === 'string' ? identity.displayName : null,\n\t\turl: null,\n\t};\n}\n\n/** Only a UTC ISO timestamp naming a real calendar date and time, as provider-apis accepts; anything else is `null`. */\nexport function parseAzureWorkItemDate(value: unknown): Date | null {\n\tif (typeof value !== 'string') return null;\n\n\tconst match = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.\\d{1,7})?Z$/.exec(value);\n\tif (match == null) return null;\n\n\tconst [year, month, day, hour, minute, second] = match.slice(1).map(Number);\n\tconst isLeapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);\n\tconst daysInMonth = [31, isLeapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\n\tif (month < 1 || month > 12 || day < 1 || day > daysInMonth[month - 1] || hour > 23 || minute > 59 || second > 59) {\n\t\treturn null;\n\t}\n\n\tconst date = new Date(value);\n\treturn Number.isNaN(date.getTime()) ? null : date;\n}\n\n/** The SDK's `iteration`, by the same path rule as {@link toWorkItemIterations}. */\nfunction toAzureWorkItemIteration(path: unknown): ProviderIssue['iteration'] {\n\tconst [iteration] = toWorkItemIterations(typeof path === 'string' ? path : undefined) ?? [];\n\treturn iteration != null ? { path: iteration.id, name: iteration.name } : undefined;\n}\n\n/**\n * The most ids one cheap work item read asks for: the most Azure DevOps' work item list (`GET …/_apis/wit/workitems`)\n * takes.\n */\nexport const azureWorkItemsEtagFieldsMaxIds = 200;\n\n/**\n * The fields the cheap work item read selects: only the ones a full row's etag reads, and the project the full read\n * checks the work item against. The state and type let the cheap read resolve the same workflow category as the full\n * read, from the same work item type states.\n */\nexport const azureWorkItemEtagFieldNames: readonly string[] = [\n\t'System.TeamProject',\n\t'System.State',\n\t'System.WorkItemType',\n\t'System.ChangedDate',\n\t'Microsoft.VSTS.Common.ClosedDate',\n];\n\n/**\n * A cheap read's work item, as the fields its full batch row's etag reads: `toIssueShape` closes the row the full\n * read builds (see {@link fromAzureWorkItemToProviderIssue}) from its known workflow category, falling back to a\n * closed date only when the category is unavailable. A historical date must not close a reopened work item.\n * Throws where the full read fails the work item: on one in another project than `project` (compared as Azure\n * compares names), and on a missing or unparseable change date, so the target falls through to the full read.\n */\nexport function toAzureWorkItemEtagFields(\n\tworkItem: AzureWorkItemResponse,\n\tproject: string,\n\tcategory: IssueProviderState['category'],\n): IssueEtagFields {\n\tconst fields = workItem.fields;\n\tconst teamProject = fields?.['System.TeamProject'];\n\tif (typeof teamProject !== 'string' || !teamProject.trim()) {\n\t\tthrow new Error(`Azure DevOps work item ${String(workItem.id)} has no project`);\n\t}\n\n\tif (teamProject.toLowerCase() !== project.toLowerCase()) {\n\t\tthrow new Error(\n\t\t\t`Azure DevOps work item ${String(workItem.id)} is in project '${teamProject}', not '${project}'`,\n\t\t);\n\t}\n\n\tconst updatedDate = parseAzureWorkItemDate(fields?.['System.ChangedDate']);\n\tif (updatedDate == null) {\n\t\tthrow new Error(`Azure DevOps returned work item ${String(workItem.id)} without a change date`);\n\t}\n\n\tconst state =\n\t\tcategory != null\n\t\t\t? isTerminalIssueStateCategory(category)\n\t\t\t\t? 'closed'\n\t\t\t\t: 'opened'\n\t\t\t: parseAzureWorkItemDate(fields?.['Microsoft.VSTS.Common.ClosedDate']) != null\n\t\t\t\t? 'closed'\n\t\t\t\t: undefined;\n\treturn {\n\t\tstate: state,\n\t\tupdatedDate: updatedDate,\n\t\tdescriptionRule: azureWorkItemDescriptionRule,\n\t\tviewerRule: azureWorkItemViewerRule,\n\t};\n}\n\ntype ProviderPullRequestState = Parameters<typeof fromProviderPullRequestState>[0];\ntype ProviderMergeableState = keyof typeof fromProviderPullRequestMergeableState;\ntype ProviderReviewState = keyof typeof fromPullRequestReviewDecision;\n\n// The tables below mirror provider-apis' (0.61.0) Azure DevOps pull request mapping behind `getPullRequestForRepo` (`Xe`\n// and its helpers in the bundle), which it doesn't export. A full batch row takes these values on through\n// `fromProviderPullRequest`, so the cheap check composes the same steps to compute the same etag.\n\n/** provider-apis' pull request `status` map (`bi`). It has no `notSet`, which therefore maps to `undefined`. */\nconst azurePullRequestProviderStates: Partial<Record<string, ProviderPullRequestState>> = {\n\tactive: GitPullRequestState.Open,\n\tcompleted: GitPullRequestState.Merged,\n\tabandoned: GitPullRequestState.Closed,\n};\n\n/** provider-apis' `mergeStatus` map (`Ti`); any other status, or none, is unknown. */\nconst azureMergeStatusProviderStates: Partial<Record<string, ProviderMergeableState>> = {\n\tconflicts: GitPullRequestMergeableState.Conflicts,\n\tfailure: GitPullRequestMergeableState.FailingChecks,\n\trejectedByPolicy: GitPullRequestMergeableState.Blocked,\n\tsucceeded: GitPullRequestMergeableState.Mergeable,\n};\n\n/** provider-apis' reviewer `vote` map (`Di`); any other vote reads as review requested. */\nconst azureVoteProviderReviewStates: Partial<Record<number, ProviderReviewState>> = {\n\t10: GitPullRequestReviewState.Approved,\n\t5: GitPullRequestReviewState.Approved,\n\t0: GitPullRequestReviewState.ReviewRequested,\n\t[-5]: GitPullRequestReviewState.ChangesRequested,\n\t[-10]: GitPullRequestReviewState.ChangesRequested,\n};\n\n/** provider-apis' reviewer mapping (`ve`), with the state its vote maps to: every reviewer, required or optional. */\nfunction toAzureProviderReview(reviewer: AzureUserWithVote): ProviderPullRequestReview {\n\treturn {\n\t\treviewer: {\n\t\t\tid: reviewer.id,\n\t\t\tname: reviewer.displayName ?? null,\n\t\t\tusername: (reviewer.uniqueName || reviewer.displayName) ?? null,\n\t\t\temail: null,\n\t\t\tavatarUrl: reviewer.imageUrl ?? null,\n\t\t\turl: null,\n\t\t},\n\t\tstate: toAzureProviderReviewState(reviewer),\n\t};\n}\n\nfunction toAzureProviderReviewState(reviewer: AzureUserWithVote): ProviderReviewState {\n\treturn azureVoteProviderReviewStates[reviewer.vote ?? 0] ?? GitPullRequestReviewState.ReviewRequested;\n}\n\n/**\n * A cheap read's pull request (the same `GET …/pullrequests/{id}` provider-apis sends), as the fields its full batch\n * row's etag reads: provider-apis' mapping (see the tables above), then `fromProviderPullRequest`'s. Azure DevOps\n * reports no update time, so provider-apis' `updatedDate` is the close time, else the creation time, which is why\n * the fields Azure changes without one are fingerprinted into the `revision`, from the values the full row ends up\n * with: provider-apis' title, description (`?? null`, which the row reads `?? undefined`) and target branch, and its\n * reviews, split into requests and completed reviews by `fromProviderPullRequest`'s own helpers.\n *\n * Throws where provider-apis' mapping throws, on a field it reads without a guard, so a pull request the full read\n * fails on falls through to it rather than matching an etag. A full row never carries a check rollup (provider-apis\n * maps none for Azure DevOps), so the `checks` include always reads `undefined`, as there.\n */\nexport function toAzurePullRequestEtagFields(\n\tpr: AzurePullRequest,\n\tetagIncludes: readonly PullRequestEtagInclude[],\n): PullRequestEtagFields {\n\tif (\n\t\tpr.pullRequestId == null ||\n\t\tpr.createdBy == null ||\n\t\tpr.repository?.project == null ||\n\t\tpr.lastMergeSourceCommit == null ||\n\t\tpr.lastMergeTargetCommit == null ||\n\t\t!Array.isArray(pr.reviewers) ||\n\t\tpr.reviewers.some(r => r == null) ||\n\t\ttypeof pr.url !== 'string' ||\n\t\ttypeof pr.sourceRefName !== 'string' ||\n\t\ttypeof pr.targetRefName !== 'string'\n\t) {\n\t\tthrow new Error(`Azure DevOps returned pull request ${String(pr.pullRequestId)} without a field it maps`);\n\t}\n\n\tconst reviews = pr.reviewers.map(toAzureProviderReview);\n\tconst fields: PullRequestEtagFields = {\n\t\t// `notSet` passes `undefined` here, as its full row does.\n\t\tstate: fromProviderPullRequestState(azurePullRequestProviderStates[pr.status] as ProviderPullRequestState),\n\t\tisDraft: pr.isDraft,\n\t\tupdatedDate: new Date(pr.closedDate || pr.creationDate),\n\t\theadSha: pr.lastMergeSourceCommit.commitId ?? '',\n\t\trevision: pullRequestRevision({\n\t\t\ttitle: pr.title,\n\t\t\tbody: pr.description ?? undefined,\n\t\t\trefs: { base: { branch: normalizeAzureBranchName(pr.targetRefName) } },\n\t\t\treviewRequests: toReviewRequests(reviews),\n\t\t\tlatestReviews: toCompletedReviews(reviews),\n\t\t}),\n\t};\n\n\tif (etagIncludes.includes('mergeable')) {\n\t\tconst mergeable =\n\t\t\t(pr.mergeStatus != null ? azureMergeStatusProviderStates[pr.mergeStatus] : undefined) ??\n\t\t\tGitPullRequestMergeableState.Unknown;\n\t\tfields.mergeableState = fromProviderPullRequestMergeableState[mergeable];\n\t}\n\n\tif (etagIncludes.includes('reviewDecision')) {\n\t\t// Only the required reviewers decide.\n\t\tconst decision = decideProviderReviewDecision(\n\t\t\tpr.reviewers.filter(r => r.isRequired).map(toAzureProviderReviewState),\n\t\t);\n\t\tfields.reviewDecision = decision ? fromPullRequestReviewDecision[decision] : undefined;\n\t}\n\n\treturn fields;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"models.js","names":["Issue","GitPullRequestState","GitPullRequestMergeableState","GitPullRequestReviewState"],"sources":["../../../../../../plus/integrations/src/providers/azure/models.ts"],"sourcesContent":["import type { AzureWorkItemHtmlField } from '@gitkraken/provider-apis';\nimport { getAzureWorkItemDescription } from '@gitkraken/provider-apis';\nimport type { IssueIteration, IssueMember, IssueProjection, IssueProviderState } from '@gitlens/git/models/issue.js';\nimport { Issue } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequestState } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type { PullRequestMember, PullRequestProjection, PullRequestReviewer } from '@gitlens/git/models/pullRequest.js';\nimport {\n\tPullRequest,\n\tPullRequestMergeableState,\n\tPullRequestReviewDecision,\n\tPullRequestReviewState,\n} from '@gitlens/git/models/pullRequest.js';\nimport type { Provider } from '@gitlens/git/models/remoteProvider.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { isTerminalIssueStateCategory } from '@gitlens/git/utils/issue.utils.js';\nimport type { IssueEtagFields, PullRequestEtagFields, PullRequestEtagInclude } from '../../models/integration.js';\nimport {\n\tazureWorkItemDescriptionRule,\n\tazureWorkItemViewerRule,\n\tpullRequestRevision,\n} from '../../models/integration.js';\nimport type { ProviderAccount, ProviderIssue } from '../models.js';\nimport {\n\tfromProviderPullRequestMergeableState,\n\tfromProviderPullRequestState,\n\tGitPullRequestMergeableState,\n\tGitPullRequestReviewState,\n\tGitPullRequestState,\n\ttoIssueBodyField,\n\ttoIssueViewer,\n} from '../models.js';\nimport type { ProviderPullRequestReview } from '../pullRequestReviews.js';\nimport {\n\tdecideProviderReviewDecision,\n\tfromPullRequestReviewDecision,\n\ttoCompletedReviews,\n\ttoReviewRequests,\n} from '../pullRequestReviews.js';\n\nexport const vstsHostnameSuffix = '.visualstudio.com';\n\nexport interface AzureRepositoryDescriptor extends ResourceDescriptor {\n\towner: string;\n\tname: string;\n\tproject?: string;\n\tvirtualDirectory?: string;\n}\n\nexport interface AzureOrganizationDescriptor extends ResourceDescriptor {\n\tid: string;\n\tname: string;\n}\n\nexport interface AzureProjectDescriptor extends ResourceDescriptor {\n\tid: string;\n\tname: string;\n\tresourceId: string;\n\tresourceName: string;\n}\n\nexport interface AzureRemoteRepositoryDescriptor extends ResourceDescriptor {\n\tid: string;\n\tnodeId?: string;\n\tresourceName: string;\n\tname: string;\n\tprojectName?: string;\n\turl?: string;\n\tcloneUrlHttps?: string;\n\tcloneUrlSsh?: string;\n}\n\nexport interface AzureProjectInputDescriptor extends ResourceDescriptor {\n\towner: string;\n\tname: string;\n}\n\nexport type AzureWorkItemStateCategory = 'Proposed' | 'InProgress' | 'Resolved' | 'Completed' | 'Removed';\n\n/**\n * The workflow category of an Azure DevOps state category, as a work item type's states endpoint reports it:\n * `Removed` is abandoned work, so `CANCELED`, kept apart from the `DONE` of `Completed` and `Resolved`.\n * `undefined` for a category Azure doesn't document, which then neither opens nor closes the work item.\n */\nexport function azureIssueStateCategory(\n\tcategory: AzureWorkItemStateCategory | undefined,\n): IssueProviderState['category'] {\n\tswitch (category) {\n\t\tcase 'Removed':\n\t\t\treturn 'CANCELED';\n\t\tcase 'Completed':\n\t\tcase 'Resolved':\n\t\t\treturn 'DONE';\n\t\tcase 'InProgress':\n\t\t\treturn 'IN_PROGRESS';\n\t\tcase 'Proposed':\n\t\t\treturn 'TO_DO';\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nexport function isClosedAzureWorkItemStateCategory(category: AzureWorkItemStateCategory | undefined): boolean {\n\treturn isTerminalIssueStateCategory(azureIssueStateCategory(category));\n}\n\nexport function azureWorkItemsStateCategoryToState(\n\tcategory: AzureWorkItemStateCategory | undefined,\n\tclosedDate?: Date,\n): IssueOrPullRequestState | undefined {\n\tif (azureIssueStateCategory(category) != null) {\n\t\treturn isClosedAzureWorkItemStateCategory(category) ? 'closed' : 'opened';\n\t}\n\n\treturn closedDate != null ? 'closed' : undefined;\n}\n\nexport interface AzureLink {\n\thref: string;\n}\n\nexport interface AzureUser {\n\tdisplayName: string;\n\turl: string;\n\t_links: {\n\t\tavatar: AzureLink;\n\t};\n\tid: string;\n\tuniqueName: string;\n\timageUrl: string;\n\tdescriptor?: string;\n}\n\nexport interface AzureUserWithVote extends AzureUser {\n\tisFlagged?: boolean;\n\thasDeclined?: boolean;\n\tisReapprove?: boolean;\n\tisRequired?: boolean;\n\tvote?: AzurePullRequestVote;\n}\n\nexport type AzurePullRequestVote =\n\t| 10 // approved\n\t| 5 // approved with suggestions\n\t| 0 // no vote\n\t| -5 // waiting for author\n\t| -10; // rejected\n\nexport interface AzureWorkItemCommentVersionRef {\n\tcommentId: number;\n\tcreatedInRevision: number;\n\tisDeleted: boolean;\n\ttext: string;\n\turl: string;\n\tversion: number;\n}\n\nexport interface AzureWorkItemRelation {\n\tattributes: {\n\t\t[key: string]: string;\n\t};\n\trelation: string;\n\turl: string;\n}\n\nexport interface WorkItem {\n\t_links: {\n\t\tfields: AzureLink;\n\t\thtml: AzureLink;\n\t\tself: AzureLink;\n\t\tworkItemComments: AzureLink;\n\t\tworkItemRevisions: AzureLink;\n\t\tworkItemType: AzureLink;\n\t\tworkItemUpdates: AzureLink;\n\t};\n\tfields: {\n\t\t//'System.AreaPath': string;\n\t\t'System.TeamProject': string;\n\t\t'System.IterationPath'?: string;\n\t\t'System.WorkItemType': string;\n\t\t'System.State': string;\n\t\t// 'System.Reason': string;\n\t\t'System.AssignedTo': AzureUser | string;\n\t\t'System.CreatedDate': string;\n\t\t'System.CreatedBy': AzureUser | string;\n\t\t'System.ChangedDate': string;\n\t\t'System.ChangedBy': AzureUser;\n\t\t'System.CommentCount': number;\n\t\t'System.Description': string;\n\t\t'System.Title': string;\n\t\t'Microsoft.VSTS.Common.ClosedDate': string;\n\t\t// 'Microsoft.VSTS.Common.StateChangeDate': string;\n\t\t// 'Microsoft.VSTS.Common.Priority': number;\n\t\t// 'Microsoft.VSTS.Common.Severity': string;\n\t\t// 'Microsoft.VSTS.Common.ValueArea': string;\n\t};\n\tid: number;\n\trev: number;\n\turl: string;\n\tcommentVersionRef?: AzureWorkItemCommentVersionRef;\n\trelations?: AzureWorkItemRelation[];\n}\n\nexport interface AzureWorkItemState {\n\tname: string;\n\tcolor: string;\n\tcategory: AzureWorkItemStateCategory;\n}\n\nexport type AzurePullRequestStatus = 'abandoned' | 'active' | 'completed' | 'notSet';\nexport function azurePullRequestStatusToState(status: AzurePullRequestStatus): IssueOrPullRequestState {\n\tswitch (status) {\n\t\tcase 'abandoned':\n\t\t\treturn 'closed';\n\t\tcase 'completed':\n\t\t\treturn 'merged';\n\t\tcase 'active':\n\t\tcase 'notSet':\n\t\tdefault:\n\t\t\treturn 'opened';\n\t}\n}\nexport function isClosedAzurePullRequestStatus(status: AzurePullRequestStatus): boolean {\n\treturn azurePullRequestStatusToState(status) !== 'opened';\n}\n\nexport type AzureProjectState = 'createPending' | 'deleted' | 'deleting' | 'new' | 'unchanged' | 'wellFormed';\nexport type AzureProjectVisibility = 'private' | 'public';\n\nexport interface AzureProject {\n\tid: string;\n\tname: string;\n\turl: string;\n\tstate: AzureProjectState;\n\trevision: number;\n\tvisibility: AzureProjectVisibility;\n\tlastUpdateTime: string;\n}\n\nexport interface AzureRepositoryReference {\n\tid: string;\n\tname: string;\n\turl: string;\n\tremoteUrl?: string;\n}\n\nexport interface AzureRepository extends AzureRepositoryReference {\n\tproject: AzureProject;\n\tsize: number;\n\tremoteUrl: string;\n\tsshUrl: string;\n\twebUrl: string;\n\tisDisabled: boolean;\n\tisInMaintenance: boolean;\n}\n\nexport interface AzurePullRequestRepository extends AzureRepositoryReference {\n\tproject: Pick<AzureProject, 'id' | 'name'>;\n}\n\n/** The URL fields of a repository response — all the fork lookup reads, and all an older `api-version` promises. */\nexport type AzureRepositoryUrls = Partial<Pick<AzureRepository, 'webUrl' | 'remoteUrl'>>;\n\n/** The `GET .../_apis/git/repositories/{repositoryId or name}` response, adding fork/default-branch fields to {@link AzureRepository}. */\nexport interface AzureRepositoryWithMetadata extends AzureRepository {\n\t/** Fully-qualified ref, e.g. `refs/heads/main`. */\n\tdefaultBranch?: string;\n\tisFork?: boolean;\n\tparentRepository?: {\n\t\tid: string;\n\t\tname?: string;\n\t\tproject?: { id: string; name: string };\n\t};\n}\n\nexport interface AzureGitUser {\n\tdate?: string;\n\temail?: string;\n\timageUrl?: string;\n\tname: string;\n}\n\nexport interface AzureGitCommitRef {\n\tcommitId: string;\n\turl: string;\n}\n\nexport interface AzureGitCommit {\n\t_links: {\n\t\tchanges: AzureLink;\n\t\trepository: AzureLink;\n\t\tself: AzureLink;\n\t\tweb: AzureLink;\n\t};\n\tauthor: AzureGitUser;\n\tcomment: string;\n\tcommentTruncated?: boolean;\n\tcommitId: string;\n\tcommitTooManyChanges?: boolean;\n\tcommitter: AzureGitUser;\n\tparents: string[];\n\tpush: {\n\t\tdate: string;\n\t\tpushedBy: AzureUser;\n\t\tpushId: number;\n\t};\n\tremoteUrl: string;\n\tstatuses?: AzureGitStatus[];\n\ttreeId: string;\n\turl: string;\n\tworkItems?: AzureResourceRef[];\n}\n\nexport interface AzureResourceRef {\n\tid: string;\n\turl: string;\n}\n\nexport interface AzurePullRequestCompletionOptions {\n\tautoCompleteIgnoreConflicts: number[];\n\tbypassPolicy: boolean;\n\tbypassReason: string;\n\tdeleteSourceBranch: boolean;\n\tmergeCommitMessage: string;\n\tmergeStrategy: 'noFastForward' | 'rebase' | 'rebaseMerge' | 'squash';\n\tsquashMerge: boolean;\n\ttransitionWorkItems: boolean;\n\ttriggeredByAutoComplete: boolean;\n}\n\nexport interface AzureGitStatus {\n\tcontext: {\n\t\tname: string;\n\t\tgenre: string;\n\t};\n\tcreatedBy: AzureUser;\n\tcreateDate: string;\n\tdescription: string;\n\tstate: 'error' | 'failed' | 'notApplicable' | 'notSet' | 'pending' | 'succeeded';\n\ttargetUrl: string;\n\tupdateDate: string;\n}\n\nexport interface AzureGitForkRef {\n\tcreator: AzureUser;\n\tisLocked: boolean;\n\tisLockedBy: AzureUser;\n\tname: string;\n\tobjectId: string;\n\tpeeledObjectId: string;\n\trepository: AzureRepositoryReference;\n\tstatuses: AzureGitStatus[];\n\turl: string;\n}\n\nexport interface AzureWebApiTagDefinition {\n\tactive: boolean;\n\tid: string;\n\tname: string;\n\turl: string;\n}\n\nexport interface AzureGitPullRequestMergeOptions {\n\tconflictAuthorshipCommits: boolean;\n\tdetectRenameFalsePositives: boolean;\n\tdisableRenames: boolean;\n}\n\nexport type AzurePullRequestAsyncStatus =\n\t| 'conflicts'\n\t| 'failure'\n\t| 'notSet'\n\t| 'queued'\n\t| 'rejectedByPolicy'\n\t| 'succeeded';\n\nexport interface AzurePullRequest {\n\trepository: AzurePullRequestRepository;\n\tpullRequestId: number;\n\tcodeReviewId: number;\n\tstatus: AzurePullRequestStatus;\n\tcreatedBy: AzureUser;\n\tcreationDate: string;\n\tclosedDate?: string;\n\tclosedBy?: AzureUser; // Can be missed even if closedDate is presented.\n\ttitle: string;\n\tdescription: string;\n\tforkSource?: AzureGitForkRef;\n\tsourceRefName: string;\n\ttargetRefName: string;\n\tisDraft: boolean;\n\tmergeId: string;\n\tmergeStatus?: AzurePullRequestAsyncStatus;\n\tlastMergeCommit?: AzureGitCommitRef;\n\tlastMergeSourceCommit: AzureGitCommitRef;\n\tlastMergeTargetCommit: AzureGitCommitRef;\n\treviewers: AzureUserWithVote[];\n\turl: string;\n\tsupportsIterations: boolean;\n}\n\nexport interface AzurePullRequestWithLinks extends AzurePullRequest {\n\t_links: {\n\t\tself: AzureLink;\n\t\trepository: AzureLink;\n\t\tworkItems: AzureLink;\n\t\tsourceBranch: AzureLink;\n\t\ttargetBranch: AzureLink;\n\t\tstatuses: AzureLink;\n\t\tsourceCommit: AzureLink;\n\t\ttargetCommit: AzureLink;\n\t\tcreatedBy: AzureLink;\n\t\titerations: AzureLink;\n\t};\n\tartifactId: string;\n\tautoCompleteSetBy?: AzureUser;\n\tcommits?: AzureGitCommitRef[];\n\tcompletionOptions?: AzurePullRequestCompletionOptions;\n\tcompletionQueueTime?: string;\n\thasMultipleMergeBases?: boolean;\n\tlabels?: AzureWebApiTagDefinition[];\n\tmergeFailureMessage?: string;\n\tmergeFailureType?: 'caseSensitive' | 'none' | 'objectTooLarge' | 'unknown';\n\tmergeOptions?: AzureGitPullRequestMergeOptions;\n\tremoteUrl?: string;\n\tworkItemRefs?: AzureResourceRef[];\n}\n\nexport function getAzureDevOpsOwner(url: URL): string {\n\treturn url.pathname.split('/')[1];\n}\nexport function isVsts(domain: string): boolean {\n\treturn domain.toLowerCase().endsWith(vstsHostnameSuffix);\n}\n\n/**\n * Encodes one route segment of an Azure DevOps request (#5878): `encodeURIComponent` leaves a dot segment as is, and\n * the URL would resolve it away, re-scoping the request instead of failing it.\n */\nexport function encodeAzurePathSegment(value: string): string {\n\tif (value === '.' || value === '..') throw new Error(`Invalid Azure path segment '${value}'.`);\n\n\treturn encodeURIComponent(value);\n}\n\n/**\n * `baseUrl` and `owner` are the authoritative prefix used for the API request. The payload URL cannot supply that\n * prefix safely because it may address the repository by id or name an untrusted host.\n */\nfunction getAzureRepositoryWebUrl(baseUrl: string, owner: string, projectName: string, repoName: string): string {\n\tconst repoPath = `${encodeURIComponent(owner)}/${encodeURIComponent(projectName)}/_git/${encodeURIComponent(repoName)}`;\n\treturn `${baseUrl.replace(/\\/+$/, '')}/${repoPath}`;\n}\n\nexport function getAzurePullRequestWebUrl(pr: AzurePullRequest, baseUrl: string, owner: string): string {\n\tconst repoUrl = getAzureRepositoryWebUrl(baseUrl, owner, pr.repository.project.name, pr.repository.name);\n\treturn `${repoUrl}/pullrequest/${pr.pullRequestId}`;\n}\n\n/**\n * Whether `url` and `expected` are the two cloud spellings of one organization: a cloud organization answers on both\n * `dev.azure.com/{owner}` and the legacy `{owner}.visualstudio.com`, so those name the same host. Anything else is\n * somewhere we didn't ask.\n */\nfunction isSameAzureCloudOrganization(url: URL, expected: URL, expectedOwner: string): boolean {\n\tif (url.protocol !== expected.protocol) return false;\n\n\tconst owner = expectedOwner.toLowerCase();\n\tif (expected.hostname !== 'dev.azure.com' && !isVsts(expected.hostname)) return false;\n\n\tif (url.hostname === 'dev.azure.com') return getAzureDevOpsOwner(url).toLowerCase() === owner;\n\n\treturn url.hostname === `${owner}${vstsHostnameSuffix}`;\n}\n\n/**\n * Whether `url` sits under the collection the integration is configured to talk to — `{expectedUrl}/{expectedOwner}`.\n * A matching origin says nothing on its own: Azure organizations and self-hosted collections are path segments.\n */\nfunction isUnderAzureCollection(url: URL, expected: URL, expectedOwner: string): boolean {\n\tconst base = expected.pathname.replace(/\\/+$/, '');\n\tconst prefix = `${base}/${encodeURIComponent(expectedOwner)}/`.toLowerCase();\n\treturn `${url.pathname}/`.toLowerCase().startsWith(prefix);\n}\n\n/**\n * Restricts a provider-supplied repository URL to the integration's collection and removes credentials, query, and\n * fragment before a consumer can use it as a git remote. Takes the value as the payloads carry it — every one of\n * these URLs is optional — so a caller never has to spell the absent case itself.\n */\nexport function sanitizeAzureRepositoryUrl(\n\tvalue: string | undefined,\n\texpectedUrl: string,\n\texpectedOwner: string,\n): string | undefined {\n\tif (value == null) return undefined;\n\n\tlet expected: URL;\n\tlet url: URL;\n\ttry {\n\t\texpected = new URL(expectedUrl);\n\t\turl = new URL(value);\n\t} catch {\n\t\treturn undefined;\n\t}\n\n\tif (expected.protocol !== 'https:' && expected.protocol !== 'http:') return undefined;\n\tif (!expectedOwner) return undefined;\n\n\tif (url.origin === expected.origin) {\n\t\tif (!isUnderAzureCollection(url, expected, expectedOwner)) return undefined;\n\t} else if (!isSameAzureCloudOrganization(url, expected, expectedOwner)) {\n\t\treturn undefined;\n\t}\n\n\turl.username = '';\n\turl.password = '';\n\turl.search = '';\n\turl.hash = '';\n\treturn url.toString();\n}\n\n/**\n * The clone URL the payload carries for a repository, restricted to the integration's collection and then\n * cross-checked against the repository that payload names.\n *\n * The collection check alone is not enough for a URL that is READ rather than built. Every other repository URL the\n * model reports is built from the authoritative prefix, so the payload cannot move it; this one cannot be built,\n * because Azure spells a repository for git the same way it spells it for the web only by convention. A URL under\n * the right collection may still name a DIFFERENT repository in it, which would leave `cloneHttps` describing one\n * repository while `url` describes another.\n *\n * Azure ends a clone URL with `_git/{repo}` on every host style — including the short `{owner}/_git/{repo}` form it\n * uses when a repository carries its project's name, and the legacy `{owner}.visualstudio.com` spelling that has no\n * organization segment at all — so the repository name is the one part comparable across all of them. A same-named\n * repository in another project of the same organization is the single substitution this cannot catch.\n */\nfunction getAzureRepositoryCloneUrl(\n\trepository: AzureRepositoryReference,\n\tbaseUrl: string,\n\towner: string,\n): string | undefined {\n\tconst sanitized = sanitizeAzureRepositoryUrl(repository.remoteUrl, baseUrl, owner);\n\tif (sanitized == null) return undefined;\n\n\t// Safe to parse: `sanitizeAzureRepositoryUrl` returns what it already parsed.\n\tconst segments = new URL(sanitized).pathname.split('/').filter(s => s.length > 0);\n\tconst name = segments.pop();\n\tif (name == null || segments.pop() !== '_git') return undefined;\n\n\tlet decoded: string;\n\ttry {\n\t\tdecoded = decodeURIComponent(name);\n\t} catch {\n\t\treturn undefined;\n\t}\n\n\t// Azure resolves project and repository names case-insensitively, so comparing them any other way would refuse a\n\t// URL the provider considers the same one.\n\treturn decoded.toLowerCase() === repository.name.toLowerCase() ? sanitized : undefined;\n}\n\nexport function fromAzurePullRequestMergeStatusToMergeableState(\n\tmergeStatus: AzurePullRequestAsyncStatus,\n): PullRequestMergeableState {\n\tswitch (mergeStatus) {\n\t\tcase 'conflicts':\n\t\t\treturn PullRequestMergeableState.Conflicting;\n\t\tcase 'failure':\n\t\t\treturn PullRequestMergeableState.FailingChecks;\n\t\tcase 'rejectedByPolicy':\n\t\t\treturn PullRequestMergeableState.BlockedByPolicy;\n\t\tcase 'succeeded':\n\t\t\treturn PullRequestMergeableState.Mergeable;\n\t\tcase 'notSet':\n\t\tcase 'queued':\n\t\tdefault:\n\t\t\treturn PullRequestMergeableState.Unknown;\n\t}\n}\n\nexport function fromAzurePullRequestVoteToReviewState(vote: AzurePullRequestVote): PullRequestReviewState {\n\tswitch (vote) {\n\t\tcase 10:\n\t\tcase 5:\n\t\t\treturn PullRequestReviewState.Approved;\n\t\tcase 0:\n\t\t\treturn PullRequestReviewState.ReviewRequested;\n\t\tcase -5:\n\t\tcase -10:\n\t\t\treturn PullRequestReviewState.ChangesRequested;\n\t\tdefault:\n\t\t\treturn PullRequestReviewState.ReviewRequested;\n\t}\n}\n\nexport function fromAzureUserWithVoteToReviewer(reviewer: AzureUserWithVote): PullRequestReviewer {\n\treturn {\n\t\tisCodeOwner: undefined,\n\t\treviewer: {\n\t\t\tavatarUrl: reviewer.imageUrl,\n\t\t\tid: reviewer.id,\n\t\t\tname: reviewer.displayName,\n\t\t\tusername: reviewer.uniqueName,\n\t\t\turl: reviewer.url,\n\t\t},\n\t\tstate: fromAzurePullRequestVoteToReviewState(reviewer.vote ?? 0),\n\t};\n}\n\nexport function getAzurePullRequestReviewDecision(\n\tvotes: AzurePullRequestVote[],\n): PullRequestReviewDecision | undefined {\n\tconst reviewStates = votes.map(vote => fromAzurePullRequestVoteToReviewState(vote));\n\tif (reviewStates.includes(PullRequestReviewState.ChangesRequested)) {\n\t\treturn PullRequestReviewDecision.ChangesRequested;\n\t}\n\n\tif (reviewStates.includes(PullRequestReviewState.ReviewRequested)) {\n\t\treturn PullRequestReviewDecision.ReviewRequired;\n\t}\n\n\tif (reviewStates.includes(PullRequestReviewState.Approved)) {\n\t\treturn PullRequestReviewDecision.Approved;\n\t}\n\n\treturn undefined;\n}\n\nexport function fromAzureReviewerToPullRequestMember(reviewer: AzureUser): PullRequestMember {\n\treturn {\n\t\tavatarUrl: reviewer.imageUrl,\n\t\tid: reviewer.id,\n\t\tname: reviewer.displayName,\n\t\tusername: reviewer.uniqueName,\n\t\turl: reviewer.url,\n\t};\n}\n\nexport function normalizeAzureBranchName(branchName: string): string {\n\treturn branchName.startsWith('refs/heads/') ? branchName.replace('refs/heads/', '') : branchName;\n}\n\nfunction fromAzureUserToMember(user: AzureUser, type: 'issue'): IssueMember;\nfunction fromAzureUserToMember(user: AzureUser, type: 'pullRequest'): PullRequestMember;\nfunction fromAzureUserToMember(user: AzureUser, _type: 'issue' | 'pullRequest'): PullRequestMember | IssueMember {\n\treturn {\n\t\tavatarUrl: user.imageUrl,\n\t\tid: user.id,\n\t\tname: user.displayName,\n\t\tusername: user.uniqueName,\n\t\turl: user.url,\n\t};\n}\n\n/**\n * The URLs of a cross-repository pull request's fork, resolved by a lookup because the pull request payload embeds\n * only an abbreviated reference to it: `url` is the URL the head ref reports — the fork's web URL, or its clone URL\n * when the response carries no web URL — and `cloneHttps` its HTTPS clone URL.\n *\n * A fork that resolves neither is reported as no fork at all rather than as an object with nothing in it, so `url`\n * is always present here; `cloneHttps` alone is best-effort, and is absent when the response omits it or its URL\n * sits outside the integration's collection.\n */\nexport interface AzureForkRepositoryUrls {\n\turl: string;\n\tcloneHttps: string | undefined;\n}\n\n/**\n * `baseUrl` and `owner` are the prefix the pull request was read through; every URL the model reports is built from\n * them. The required `forkRepositoryUrls` argument makes each call site resolve the best-effort fork URLs explicitly.\n */\nexport function fromAzurePullRequest(\n\tpr: AzurePullRequest,\n\tprovider: Provider,\n\towner: string,\n\tbaseUrl: string,\n\tforkRepositoryUrls: AzureForkRepositoryUrls | undefined,\n\tprojection?: PullRequestProjection,\n): PullRequest {\n\tconst baseRepositoryUrl = getAzureRepositoryWebUrl(baseUrl, owner, pr.repository.project.name, pr.repository.name);\n\tconst baseCloneHttps = getAzureRepositoryCloneUrl(pr.repository, baseUrl, owner);\n\n\tconst forkRepository = pr.forkSource?.repository;\n\tconst headRepositoryUrl = forkRepository == null ? baseRepositoryUrl : forkRepositoryUrls?.url;\n\tconst headCloneHttps = forkRepository == null ? baseCloneHttps : forkRepositoryUrls?.cloneHttps;\n\n\treturn new PullRequest(\n\t\tprovider,\n\t\tfromAzureUserToMember(pr.createdBy, 'pullRequest'),\n\t\tpr.pullRequestId.toString(),\n\t\tpr.pullRequestId.toString(),\n\t\tpr.title,\n\t\tgetAzurePullRequestWebUrl(pr, baseUrl, owner),\n\t\t{\n\t\t\towner: owner,\n\t\t\trepo: pr.repository.name,\n\t\t\tid: pr.repository.id,\n\t\t},\n\t\tazurePullRequestStatusToState(pr.status),\n\t\tnew Date(pr.creationDate),\n\t\tnew Date(pr.closedDate || pr.creationDate),\n\t\tpr.closedDate ? new Date(pr.closedDate) : undefined,\n\t\tpr.closedDate && pr.status === 'completed' ? new Date(pr.closedDate) : undefined,\n\t\t// The read selects the merge status, so an absent one is Azure's own \"not set\", as provider-apis maps it.\n\t\tfromAzurePullRequestMergeStatusToMergeableState(pr.mergeStatus ?? 'notSet'),\n\t\tundefined,\n\t\t{\n\t\t\tbase: {\n\t\t\t\tbranch: pr.targetRefName ? normalizeAzureBranchName(pr.targetRefName) : '',\n\t\t\t\tsha: pr.lastMergeTargetCommit?.commitId ?? '',\n\t\t\t\trepo: pr.repository.name,\n\t\t\t\towner: owner,\n\t\t\t\texists: pr.targetRefName != null,\n\t\t\t\turl: baseRepositoryUrl,\n\t\t\t\tcloneHttps: baseCloneHttps,\n\t\t\t},\n\t\t\thead: {\n\t\t\t\tbranch: pr.sourceRefName ? normalizeAzureBranchName(pr.sourceRefName) : '',\n\t\t\t\tsha: pr.lastMergeSourceCommit?.commitId ?? '',\n\t\t\t\trepo: forkRepository?.name ?? pr.repository.name,\n\t\t\t\towner: owner,\n\t\t\t\texists: pr.sourceRefName != null,\n\t\t\t\turl: headRepositoryUrl,\n\t\t\t\tcloneHttps: headCloneHttps,\n\t\t\t},\n\t\t\tisCrossRepository: pr.forkSource != null,\n\t\t},\n\t\tpr.isDraft,\n\t\tundefined,\n\t\tundefined,\n\t\tundefined,\n\t\tundefined,\n\t\tgetAzurePullRequestReviewDecision(pr.reviewers?.filter(r => r.isRequired).map(r => r.vote ?? 0) ?? []),\n\t\tpr.reviewers.filter(r => r.vote == null || r.vote === 0).map(r => fromAzureUserWithVoteToReviewer(r)),\n\t\tpr.reviewers.filter(r => r.vote != null && r.vote !== 0).map(r => fromAzureUserWithVoteToReviewer(r)),\n\t\tpr.reviewers.map(r => fromAzureReviewerToPullRequestMember(r)),\n\t\tundefined,\n\t\t{\n\t\t\tid: pr.repository?.project?.id,\n\t\t\tname: pr.repository.project.name,\n\t\t\tresourceId: '', // TODO: This is a workaround until we can get the org id here.\n\t\t\tresourceName: owner,\n\t\t},\n\t\tundefined, // version\n\t\tundefined, // commitCount\n\t\tundefined, // stack\n\t\tundefined, // filesChanged\n\t\tundefined, // body\n\t\tundefined, // number\n\t\tundefined, // authoredByMe\n\t\tprojection,\n\t);\n}\n\nfunction fromAzureWorkItemUserToMember(user: AzureUser | string | undefined): IssueMember | undefined {\n\tif (user == null || (typeof user === 'string' && !user.trim())) return undefined;\n\n\treturn typeof user === 'string' ? { id: user, name: user, username: user } : fromAzureUserToMember(user, 'issue');\n}\n\nexport function fromAzureWorkItem(\n\tworkItem: WorkItem,\n\tprovider: Provider,\n\tproject: AzureProjectDescriptor,\n\tstateCategory?: AzureWorkItemStateCategory,\n\tprojection?: IssueProjection,\n\thtmlFields?: readonly AzureWorkItemHtmlField[],\n\t/** As `toIssueShape` takes it: `null` where the read couldn't resolve it. */\n\tviewer?: { id: string } | null,\n): Issue {\n\tconst state = workItem.fields['System.State'];\n\tconst description = getAzureWorkItemDescription(workItem.fields, htmlFields);\n\tconst closedDate = parseAzureWorkItemDate(workItem.fields['Microsoft.VSTS.Common.ClosedDate']) ?? undefined;\n\tconst normalizedState = azureWorkItemsStateCategoryToState(stateCategory, closedDate);\n\tconst author = fromAzureWorkItemUserToMember(workItem.fields['System.CreatedBy']);\n\tconst assignee = fromAzureWorkItemUserToMember(workItem.fields['System.AssignedTo']);\n\tconst assignees = assignee != null ? [assignee] : [];\n\tconst viewerFields = toIssueViewer({ author: author, assignees: assignees }, viewer);\n\treturn new Issue(\n\t\tprovider,\n\t\tworkItem.id.toString(),\n\t\tworkItem.id.toString(),\n\t\tworkItem.fields['System.Title'],\n\t\tworkItem._links.html.href,\n\t\tnew Date(workItem.fields['System.CreatedDate']),\n\t\tnew Date(workItem.fields['System.ChangedDate']),\n\t\tnormalizedState === 'closed',\n\t\tnormalizedState,\n\t\tauthor,\n\t\tassignees,\n\t\tundefined,\n\t\tclosedDate,\n\t\tundefined,\n\t\tworkItem.fields['System.CommentCount'],\n\t\tundefined,\n\t\tdescription?.value,\n\t\tproject,\n\t\tundefined,\n\t\tworkItem.fields['System.WorkItemType'],\n\t\ttypeof state === 'string' && state\n\t\t\t? { name: state, category: azureIssueStateCategory(stateCategory) }\n\t\t\t: undefined,\n\t\tundefined,\n\t\ttoWorkItemIterations(workItem.fields['System.IterationPath']),\n\t\tprojection,\n\t\tdescription != null ? toIssueBodyField(description.field) : undefined,\n\t\tviewerFields?.viewer,\n\t\tviewerFields?.viewerUnresolved,\n\t);\n}\n\n/**\n * Azure reports the iteration as a backslash-delimited path rooted at the project. The project root is its default\n * for a work item with no sprint, so only a nested path names one; the path is the identity because Azure supplies\n * no iteration id here.\n *\n * Mirrors `normalizeIteration` in provider-apis so the same work item yields the same identity whether it is read\n * here or through the SDK. That contract keeps the path **verbatim** — it is what the iteration is matched back by,\n * so it is deliberately not trimmed — and trims only the display name.\n */\nfunction toWorkItemIterations(path: string | undefined): IssueIteration[] | undefined {\n\tif (!path) return undefined;\n\n\tconst segments = path.split('\\\\');\n\tif (segments.length < 2) return undefined;\n\n\tconst name = segments.at(-1)?.trim();\n\treturn name ? [{ id: path, name: name }] : undefined;\n}\n\n/**\n * A work item as `GET …/_apis/wit/workitems/{id}?$expand=Links` returns it. Loosely typed because\n * {@link fromAzureWorkItemToProviderIssue} checks every field it reads, as provider-apis does.\n */\nexport interface AzureWorkItemResponse {\n\tid?: unknown;\n\tfields?: Record<string, unknown>;\n\t_links?: { html?: { href?: unknown } };\n}\n\n/**\n * A cheap read's work item with the workflow category its state classifies to (see\n * {@link toAzureWorkItemEtagFields}). Beside the work item rather than on it, so the wire type stays what Azure sends.\n */\nexport interface AzureWorkItemEtagRead {\n\treadonly workItem: AzureWorkItemResponse;\n\treadonly category: IssueProviderState['category'];\n}\n\n/**\n * Converts a work item as provider-apis' `getIssuesForAzureProject` converts each row it reads, so a work item read\n * by id reaches `toIssueShape` identical to the list read's row for it. provider-apis exports neither that converter\n * nor a single work item read, so this mirrors it field for field; the batch issue tests compare the two, so a\n * provider-apis change that this misses fails there.\n *\n * `undefined` for a work item the SDK would skip: no positive integer id, no title, or no valid created date.\n * `state.name` is the raw `System.State`, as on every list read here, which passes the SDK no state-name map. Like\n * the SDK's row, the state carries no workflow category: `ProvidersApi` classifies both reads' rows the same way\n * afterwards, from the work item type's states.\n */\nexport function fromAzureWorkItemToProviderIssue(\n\tworkItem: AzureWorkItemResponse,\n\tnamespace: string,\n\tproject: string,\n\thtmlFields?: readonly AzureWorkItemHtmlField[],\n): ProviderIssue | undefined {\n\tconst fields = workItem.fields;\n\tconst id = workItem.id;\n\tconst title = fields?.['System.Title'];\n\tconst createdDate = parseAzureWorkItemDate(fields?.['System.CreatedDate']);\n\tif (\n\t\tfields == null ||\n\t\ttypeof id !== 'number' ||\n\t\t!Number.isSafeInteger(id) ||\n\t\tid <= 0 ||\n\t\ttypeof title !== 'string' ||\n\t\t!title.trim() ||\n\t\tcreatedDate == null\n\t) {\n\t\treturn undefined;\n\t}\n\n\tconst assignee = fromAzureWorkItemIdentity(fields['System.AssignedTo']);\n\tconst commentCount = fields['System.CommentCount'];\n\tconst url = workItem._links?.html?.href;\n\tconst tags = fields['System.Tags'];\n\tconst state = fields['System.State'];\n\tconst type = fields['System.WorkItemType'];\n\treturn {\n\t\tid: id.toString(),\n\t\tnumber: id.toString(),\n\t\ttitle: title,\n\t\tcommentCount: typeof commentCount === 'number' && Number.isFinite(commentCount) ? commentCount : null,\n\t\tauthor: fromAzureWorkItemIdentity(fields['System.CreatedBy']),\n\t\tclosedDate: parseAzureWorkItemDate(fields['Microsoft.VSTS.Common.ClosedDate']),\n\t\tcreatedDate: createdDate,\n\t\tupdatedDate: parseAzureWorkItemDate(fields['System.ChangedDate']),\n\t\turl: typeof url === 'string' ? url : null,\n\t\tassignees: assignee != null ? [assignee] : [],\n\t\t...toAzureWorkItemDescriptionFields(fields, htmlFields),\n\t\tstate: typeof state === 'string' && state ? { name: state, color: null } : null,\n\t\ttype: typeof type === 'string' ? type : null,\n\t\titeration: toAzureWorkItemIteration(fields['System.IterationPath']),\n\t\trepository: null,\n\t\tproject: { namespace: namespace, name: project, resourceId: null, key: null, id: null },\n\t\t// provider-apis fills 0, but work items have no reactions.\n\t\tupvoteCount: null,\n\t\tlabels: (typeof tags === 'string' ? tags.split(';') : []).map(tag => ({\n\t\t\tcolor: null,\n\t\t\tdescription: null,\n\t\t\tid: null,\n\t\t\tname: tag.trim(),\n\t\t})),\n\t};\n}\n\nexport function toAzureWorkItemDescriptionFields(\n\tfields: Readonly<Record<string, unknown>> | null | undefined,\n\thtmlFields?: readonly AzureWorkItemHtmlField[],\n): Pick<ProviderIssue, 'description' | 'descriptionField'> {\n\tconst description = getAzureWorkItemDescription(fields, htmlFields);\n\treturn {\n\t\tdescription: description?.value ?? null,\n\t\t...(description != null ? { descriptionField: description.field } : {}),\n\t};\n}\n\n/** An identity field, as provider-apis maps it: `name` from `uniqueName`, `username` from `displayName`. */\nfunction fromAzureWorkItemIdentity(value: unknown): ProviderAccount | null {\n\tif (typeof value === 'string') {\n\t\tif (!value.trim()) return null;\n\n\t\treturn { id: value, name: value, username: value, email: null, avatarUrl: null, url: null };\n\t}\n\n\tconst identity = value as\n\t\t| { id?: unknown; uniqueName?: unknown; displayName?: unknown; _links?: { avatar?: { href?: unknown } } }\n\t\t| null\n\t\t| undefined;\n\tif (typeof identity?.id !== 'string' || !identity.id.trim()) return null;\n\n\tconst avatarUrl = identity._links?.avatar?.href;\n\treturn {\n\t\tavatarUrl: typeof avatarUrl === 'string' ? avatarUrl : null,\n\t\temail: null,\n\t\tid: identity.id,\n\t\tname: typeof identity.uniqueName === 'string' ? identity.uniqueName : null,\n\t\tusername: typeof identity.displayName === 'string' ? identity.displayName : null,\n\t\turl: null,\n\t};\n}\n\n/** Only a UTC ISO timestamp naming a real calendar date and time, as provider-apis accepts; anything else is `null`. */\nexport function parseAzureWorkItemDate(value: unknown): Date | null {\n\tif (typeof value !== 'string') return null;\n\n\tconst match = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.\\d{1,7})?Z$/.exec(value);\n\tif (match == null) return null;\n\n\tconst [year, month, day, hour, minute, second] = match.slice(1).map(Number);\n\tconst isLeapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);\n\tconst daysInMonth = [31, isLeapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\n\tif (month < 1 || month > 12 || day < 1 || day > daysInMonth[month - 1] || hour > 23 || minute > 59 || second > 59) {\n\t\treturn null;\n\t}\n\n\tconst date = new Date(value);\n\treturn Number.isNaN(date.getTime()) ? null : date;\n}\n\n/** The SDK's `iteration`, by the same path rule as {@link toWorkItemIterations}. */\nfunction toAzureWorkItemIteration(path: unknown): ProviderIssue['iteration'] {\n\tconst [iteration] = toWorkItemIterations(typeof path === 'string' ? path : undefined) ?? [];\n\treturn iteration != null ? { path: iteration.id, name: iteration.name } : undefined;\n}\n\n/**\n * The most ids one cheap work item read asks for: the most Azure DevOps' work item list (`GET …/_apis/wit/workitems`)\n * takes.\n */\nexport const azureWorkItemsEtagFieldsMaxIds = 200;\n\n/**\n * The fields the cheap work item read selects: only the ones a full row's etag reads, and the project the full read\n * checks the work item against. The state and type let the cheap read resolve the same workflow category as the full\n * read, from the same work item type states.\n */\nexport const azureWorkItemEtagFieldNames: readonly string[] = [\n\t'System.TeamProject',\n\t'System.State',\n\t'System.WorkItemType',\n\t'System.ChangedDate',\n\t'Microsoft.VSTS.Common.ClosedDate',\n];\n\n/**\n * A cheap read's work item, as the fields its full batch row's etag reads: `toIssueShape` closes the row the full\n * read builds (see {@link fromAzureWorkItemToProviderIssue}) from its known workflow category, falling back to a\n * closed date only when the category is unavailable. A historical date must not close a reopened work item.\n * Throws where the full read fails the work item: on one in another project than `project` (compared as Azure\n * compares names), and on a missing or unparseable change date, so the target falls through to the full read.\n */\nexport function toAzureWorkItemEtagFields(\n\tworkItem: AzureWorkItemResponse,\n\tproject: string,\n\tcategory: IssueProviderState['category'],\n): IssueEtagFields {\n\tconst fields = workItem.fields;\n\tconst teamProject = fields?.['System.TeamProject'];\n\tif (typeof teamProject !== 'string' || !teamProject.trim()) {\n\t\tthrow new Error(`Azure DevOps work item ${String(workItem.id)} has no project`);\n\t}\n\n\tif (teamProject.toLowerCase() !== project.toLowerCase()) {\n\t\tthrow new Error(\n\t\t\t`Azure DevOps work item ${String(workItem.id)} is in project '${teamProject}', not '${project}'`,\n\t\t);\n\t}\n\n\tconst updatedDate = parseAzureWorkItemDate(fields?.['System.ChangedDate']);\n\tif (updatedDate == null) {\n\t\tthrow new Error(`Azure DevOps returned work item ${String(workItem.id)} without a change date`);\n\t}\n\n\tconst state =\n\t\tcategory != null\n\t\t\t? isTerminalIssueStateCategory(category)\n\t\t\t\t? 'closed'\n\t\t\t\t: 'opened'\n\t\t\t: parseAzureWorkItemDate(fields?.['Microsoft.VSTS.Common.ClosedDate']) != null\n\t\t\t\t? 'closed'\n\t\t\t\t: undefined;\n\treturn {\n\t\tstate: state,\n\t\tupdatedDate: updatedDate,\n\t\tdescriptionRule: azureWorkItemDescriptionRule,\n\t\tviewerRule: azureWorkItemViewerRule,\n\t};\n}\n\ntype ProviderPullRequestState = Parameters<typeof fromProviderPullRequestState>[0];\ntype ProviderMergeableState = keyof typeof fromProviderPullRequestMergeableState;\ntype ProviderReviewState = keyof typeof fromPullRequestReviewDecision;\n\n// The tables below mirror provider-apis' (0.61.0) Azure DevOps pull request mapping behind `getPullRequestForRepo` (`Xe`\n// and its helpers in the bundle), which it doesn't export. A full batch row takes these values on through\n// `fromProviderPullRequest`, so the cheap check composes the same steps to compute the same etag.\n\n/** provider-apis' pull request `status` map (`bi`). It has no `notSet`, which therefore maps to `undefined`. */\nconst azurePullRequestProviderStates: Partial<Record<string, ProviderPullRequestState>> = {\n\tactive: GitPullRequestState.Open,\n\tcompleted: GitPullRequestState.Merged,\n\tabandoned: GitPullRequestState.Closed,\n};\n\n/** provider-apis' `mergeStatus` map (`Ti`); any other status, or none, is unknown. */\nconst azureMergeStatusProviderStates: Partial<Record<string, ProviderMergeableState>> = {\n\tconflicts: GitPullRequestMergeableState.Conflicts,\n\tfailure: GitPullRequestMergeableState.FailingChecks,\n\trejectedByPolicy: GitPullRequestMergeableState.Blocked,\n\tsucceeded: GitPullRequestMergeableState.Mergeable,\n};\n\n/** provider-apis' reviewer `vote` map (`Di`); any other vote reads as review requested. */\nconst azureVoteProviderReviewStates: Partial<Record<number, ProviderReviewState>> = {\n\t10: GitPullRequestReviewState.Approved,\n\t5: GitPullRequestReviewState.Approved,\n\t0: GitPullRequestReviewState.ReviewRequested,\n\t[-5]: GitPullRequestReviewState.ChangesRequested,\n\t[-10]: GitPullRequestReviewState.ChangesRequested,\n};\n\n/** provider-apis' reviewer mapping (`ve`), with the state its vote maps to: every reviewer, required or optional. */\nfunction toAzureProviderReview(reviewer: AzureUserWithVote): ProviderPullRequestReview {\n\treturn {\n\t\treviewer: {\n\t\t\tid: reviewer.id,\n\t\t\tname: reviewer.displayName ?? null,\n\t\t\tusername: (reviewer.uniqueName || reviewer.displayName) ?? null,\n\t\t\temail: null,\n\t\t\tavatarUrl: reviewer.imageUrl ?? null,\n\t\t\turl: null,\n\t\t},\n\t\tstate: toAzureProviderReviewState(reviewer),\n\t};\n}\n\nfunction toAzureProviderReviewState(reviewer: AzureUserWithVote): ProviderReviewState {\n\treturn azureVoteProviderReviewStates[reviewer.vote ?? 0] ?? GitPullRequestReviewState.ReviewRequested;\n}\n\n/**\n * A cheap read's pull request (the same `GET …/pullrequests/{id}` provider-apis sends), as the fields its full batch\n * row's etag reads: provider-apis' mapping (see the tables above), then `fromProviderPullRequest`'s. Azure DevOps\n * reports no update time, so provider-apis' `updatedDate` is the close time, else the creation time, which is why\n * the fields Azure changes without one are fingerprinted into the `revision`, from the values the full row ends up\n * with: provider-apis' title, description (`?? null`, which the row reads `?? undefined`) and target branch, and its\n * reviews, split into requests and completed reviews by `fromProviderPullRequest`'s own helpers.\n *\n * Throws where provider-apis' mapping throws, on a field it reads without a guard, so a pull request the full read\n * fails on falls through to it rather than matching an etag. A full row never carries a check rollup (provider-apis\n * maps none for Azure DevOps), so the `checks` include always reads `undefined`, as there.\n */\nexport function toAzurePullRequestEtagFields(\n\tpr: AzurePullRequest,\n\tetagIncludes: readonly PullRequestEtagInclude[],\n): PullRequestEtagFields {\n\tif (\n\t\tpr.pullRequestId == null ||\n\t\tpr.createdBy == null ||\n\t\tpr.repository?.project == null ||\n\t\tpr.lastMergeSourceCommit == null ||\n\t\tpr.lastMergeTargetCommit == null ||\n\t\t!Array.isArray(pr.reviewers) ||\n\t\tpr.reviewers.some(r => r == null) ||\n\t\ttypeof pr.url !== 'string' ||\n\t\ttypeof pr.sourceRefName !== 'string' ||\n\t\ttypeof pr.targetRefName !== 'string'\n\t) {\n\t\tthrow new Error(`Azure DevOps returned pull request ${String(pr.pullRequestId)} without a field it maps`);\n\t}\n\n\tconst reviews = pr.reviewers.map(toAzureProviderReview);\n\tconst fields: PullRequestEtagFields = {\n\t\t// `notSet` passes `undefined` here, as its full row does.\n\t\tstate: fromProviderPullRequestState(azurePullRequestProviderStates[pr.status] as ProviderPullRequestState),\n\t\tisDraft: pr.isDraft,\n\t\tupdatedDate: new Date(pr.closedDate || pr.creationDate),\n\t\theadSha: pr.lastMergeSourceCommit.commitId ?? '',\n\t\trevision: pullRequestRevision({\n\t\t\ttitle: pr.title,\n\t\t\tbody: pr.description ?? undefined,\n\t\t\trefs: { base: { branch: normalizeAzureBranchName(pr.targetRefName) } },\n\t\t\treviewRequests: toReviewRequests(reviews),\n\t\t\tlatestReviews: toCompletedReviews(reviews),\n\t\t}),\n\t};\n\n\tif (etagIncludes.includes('mergeable')) {\n\t\tconst mergeable =\n\t\t\t(pr.mergeStatus != null ? azureMergeStatusProviderStates[pr.mergeStatus] : undefined) ??\n\t\t\tGitPullRequestMergeableState.Unknown;\n\t\tfields.mergeableState = fromProviderPullRequestMergeableState[mergeable];\n\t}\n\n\tif (etagIncludes.includes('reviewDecision')) {\n\t\t// Only the required reviewers decide.\n\t\tconst decision = decideProviderReviewDecision(\n\t\t\tpr.reviewers.filter(r => r.isRequired).map(toAzureProviderReviewState),\n\t\t);\n\t\tfields.reviewDecision = decision ? fromPullRequestReviewDecision[decision] : undefined;\n\t}\n\n\treturn fields;\n}\n"],"mappings":"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"}
@@ -118,13 +118,13 @@ export declare abstract class AzureDevOpsIntegrationBase<TIntegrationId extends
118
118
  protected requestResourcesForUser(session: ProviderAuthenticationSession, userId: string): Promise<ProviderAzureResource[] | undefined>;
119
119
  private readonly _organizationUserIds;
120
120
  /**
121
- * How long a read waits for the user's identity in an organization (its `ConnectionData`) before setting its rows'
122
- * viewer without it: a hung lookup must not hold up the rows it only enriches.
121
+ * How long a read waits for the user's identity in an organization (its `ConnectionData`) before marking its rows
122
+ * `viewerUnresolved` instead: a hung lookup must not hold up the rows it only enriches.
123
123
  */
124
124
  protected organizationViewerTimeoutMs: number;
125
125
  /**
126
126
  * The {@link getFilterUserId} lookups that outlived {@link organizationViewerTimeoutMs} and are still pending. A
127
- * read resolves no viewer for them straight away rather than waiting on the same hung request again; each is
127
+ * read treats them as unresolved straight away rather than waiting on the same hung request again; each is
128
128
  * dropped once its own request settles, never by an older one a re-sync left behind.
129
129
  */
130
130
  private readonly _hungOrganizationUserIds;
@@ -165,31 +165,33 @@ export declare abstract class AzureDevOpsIntegrationBase<TIntegrationId extends
165
165
  /**
166
166
  * Resolved per organization, which a raw row names as its repository owner: an organization's pull requests carry
167
167
  * the id {@link getFilterUserId} resolves there, not necessarily the account's. A row an account-wide read returned
168
- * keeps the one that read resolved, even once its session is gone. A row that names no organization or no one to
169
- * match gets no viewer, nor does one in an organization whose id can't be read: its authorship stays unknown.
168
+ * keeps the one that read resolved, even once its session is gone. A row that names no one to match gets no viewer;
169
+ * one whose organization's id can't be read, that names no organization, or that no session can be resolved to ask
170
+ * for, gets `null`, so it's marked `viewerUnresolved`.
170
171
  */
171
172
  getPullRequestViewers(pullRequests: readonly (ProviderPullRequest | PullRequest)[], connectionId?: string): Promise<({
172
173
  id: string;
173
174
  username?: string;
174
- } | undefined)[]>;
175
+ } | null | undefined)[]>;
175
176
  /**
176
177
  * Resolved per organization, which a raw work item names as its project's namespace: an organization's work items
177
- * carry the id {@link getFilterUserId} resolves there, not necessarily the account's. A row that names no
178
- * organization or no one to match gets no viewer, nor does one in an organization whose id can't be read.
178
+ * carry the id {@link getFilterUserId} resolves there, not necessarily the account's. A row that names no one to
179
+ * match gets no viewer; one in an organization whose id can't be read, or that names no organization to ask, gets
180
+ * `null`.
179
181
  */
180
182
  protected getIssueViewers(session: ProviderAuthenticationSession, issues: readonly ProviderIssue[]): Promise<({
181
183
  id: string;
182
- } | undefined)[]>;
184
+ } | null | undefined)[]>;
183
185
  /**
184
186
  * For one read, the current user as each organization's pull requests and work items identify them (see
185
187
  * {@link getFilterUserId}). An organization whose id can't be read, or not within
186
- * {@link organizationViewerTimeoutMs}, resolves `undefined`, since authorship is optional enrichment and one
187
- * organization's failed or hung lookup must not fail or hold up the read or blank the others; it is asked once per
188
- * read, as a miss isn't cached.
188
+ * {@link organizationViewerTimeoutMs}, resolves `null`, which marks its rows `viewerUnresolved`: authorship is
189
+ * optional enrichment, and one organization's failed or hung lookup must not fail or hold up the read or blank the
190
+ * others. It is asked once per read, as a miss isn't cached.
189
191
  */
190
192
  protected getOrganizationViewers(session: ProviderAuthenticationSession): (organization: string) => Promise<{
191
193
  id: string;
192
- } | undefined>;
194
+ } | null>;
193
195
  /** One organization's viewer for {@link getOrganizationViewers}: bounded, never rejects. */
194
196
  private getOrganizationViewer;
195
197
  /**
@@ -207,13 +207,13 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
207
207
  }
208
208
  _organizationUserIds = new PromiseCache({ capacity: 50 });
209
209
  /**
210
- * How long a read waits for the user's identity in an organization (its `ConnectionData`) before setting its rows'
211
- * viewer without it: a hung lookup must not hold up the rows it only enriches.
210
+ * How long a read waits for the user's identity in an organization (its `ConnectionData`) before marking its rows
211
+ * `viewerUnresolved` instead: a hung lookup must not hold up the rows it only enriches.
212
212
  */
213
213
  organizationViewerTimeoutMs = 1e4;
214
214
  /**
215
215
  * The {@link getFilterUserId} lookups that outlived {@link organizationViewerTimeoutMs} and are still pending. A
216
- * read resolves no viewer for them straight away rather than waiting on the same hung request again; each is
216
+ * read treats them as unresolved straight away rather than waiting on the same hung request again; each is
217
217
  * dropped once its own request settles, never by an older one a re-sync left behind.
218
218
  */
219
219
  _hungOrganizationUserIds = /* @__PURE__ */ new Map();
@@ -290,37 +290,41 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
290
290
  /**
291
291
  * Resolved per organization, which a raw row names as its repository owner: an organization's pull requests carry
292
292
  * the id {@link getFilterUserId} resolves there, not necessarily the account's. A row an account-wide read returned
293
- * keeps the one that read resolved, even once its session is gone. A row that names no organization or no one to
294
- * match gets no viewer, nor does one in an organization whose id can't be read: its authorship stays unknown.
293
+ * keeps the one that read resolved, even once its session is gone. A row that names no one to match gets no viewer;
294
+ * one whose organization's id can't be read, that names no organization, or that no session can be resolved to ask
295
+ * for, gets `null`, so it's marked `viewerUnresolved`.
295
296
  */
296
297
  async getPullRequestViewers(pullRequests, connectionId) {
297
298
  const read = pullRequests.map((pr) => isNativePullRequest(pr) ? void 0 : this._rowViewers.get(pr));
298
- const organizations = pullRequests.map((pr, i) => read[i] == null && !isNativePullRequest(pr) && pullRequestNamesPeople(pr) ? pr.repository?.owner?.login || void 0 : void 0);
299
- if (!organizations.some((o) => o != null)) return read;
299
+ const unresolved = pullRequests.map((pr, i) => read[i] == null && !isNativePullRequest(pr) && pullRequestNamesPeople(pr));
300
+ if (!unresolved.some(Boolean)) return read;
300
301
  const session = await this.resolveReadSessionOrThrow(connectionId, void 0).catch(() => void 0);
301
- if (session == null) return read;
302
- const viewerFor = this.getOrganizationViewers(session);
303
- return Promise.all(read.map((viewer, i) => {
304
- const organization = organizations[i];
305
- if (organization == null) return Promise.resolve(viewer);
306
- return viewerFor(organization);
302
+ const viewerFor = session != null ? this.getOrganizationViewers(session) : void 0;
303
+ return Promise.all(pullRequests.map((pr, i) => {
304
+ if (!unresolved[i]) return Promise.resolve(read[i]);
305
+ const organization = pr.repository?.owner?.login;
306
+ return organization && viewerFor != null ? viewerFor(organization) : Promise.resolve(null);
307
307
  }));
308
308
  }
309
309
  /**
310
310
  * Resolved per organization, which a raw work item names as its project's namespace: an organization's work items
311
- * carry the id {@link getFilterUserId} resolves there, not necessarily the account's. A row that names no
312
- * organization or no one to match gets no viewer, nor does one in an organization whose id can't be read.
311
+ * carry the id {@link getFilterUserId} resolves there, not necessarily the account's. A row that names no one to
312
+ * match gets no viewer; one in an organization whose id can't be read, or that names no organization to ask, gets
313
+ * `null`.
313
314
  */
314
315
  getIssueViewers(session, issues) {
315
316
  const viewerFor = this.getOrganizationViewers(session);
316
- return Promise.all(issues.map((i) => issueNamesPeople(i) && i.project?.namespace ? viewerFor(i.project.namespace) : Promise.resolve(void 0)));
317
+ return Promise.all(issues.map((i) => {
318
+ if (!issueNamesPeople(i)) return Promise.resolve(void 0);
319
+ return i.project?.namespace ? viewerFor(i.project.namespace) : Promise.resolve(null);
320
+ }));
317
321
  }
318
322
  /**
319
323
  * For one read, the current user as each organization's pull requests and work items identify them (see
320
324
  * {@link getFilterUserId}). An organization whose id can't be read, or not within
321
- * {@link organizationViewerTimeoutMs}, resolves `undefined`, since authorship is optional enrichment and one
322
- * organization's failed or hung lookup must not fail or hold up the read or blank the others; it is asked once per
323
- * read, as a miss isn't cached.
325
+ * {@link organizationViewerTimeoutMs}, resolves `null`, which marks its rows `viewerUnresolved`: authorship is
326
+ * optional enrichment, and one organization's failed or hung lookup must not fail or hold up the read or blank the
327
+ * others. It is asked once per read, as a miss isn't cached.
324
328
  */
325
329
  getOrganizationViewers(session) {
326
330
  const viewers = /* @__PURE__ */ new Map();
@@ -337,12 +341,12 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
337
341
  /** One organization's viewer for {@link getOrganizationViewers}: bounded, never rejects. */
338
342
  async getOrganizationViewer(session, organization) {
339
343
  const key = this.organizationUserIdKey(session, organization);
340
- if (this._hungOrganizationUserIds.has(key)) return void 0;
344
+ if (this._hungOrganizationUserIds.has(key)) return null;
341
345
  const generation = this._organizationUserIdsGeneration;
342
346
  const lookup = this.getFilterUserId(session, organization);
343
347
  try {
344
348
  const id = await raceWithTimeout(lookup, this.organizationViewerTimeoutMs);
345
- return id != null ? { id } : void 0;
349
+ return id != null ? { id } : null;
346
350
  } catch (ex) {
347
351
  if (isCancellationError(ex) && generation === this._organizationUserIdsGeneration && !this._hungOrganizationUserIds.has(key)) {
348
352
  this._hungOrganizationUserIds.set(key, lookup);
@@ -351,7 +355,7 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
351
355
  };
352
356
  lookup.then(settled, settled);
353
357
  }
354
- return;
358
+ return null;
355
359
  }
356
360
  }
357
361
  /**