@gitkraken/core-gitlens 0.15.2 → 0.15.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"models.js","names":[],"sources":["../../../../../../plus/integrations/src/providers/azure/models.ts"],"sourcesContent":["import 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 { pullRequestRevision } from '../../models/integration.js';\nimport type { ProviderAccount, ProviderIssue } from '../models.js';\nimport {\n\tfromProviderPullRequestMergeableState,\n\tfromProviderPullRequestState,\n\tGitPullRequestMergeableState,\n\tGitPullRequestReviewState,\n\tGitPullRequestState,\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): IssueOrPullRequestState {\n\treturn isClosedAzureWorkItemStateCategory(category) ? 'closed' : 'opened';\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;\n\t\t'System.CreatedDate': string;\n\t\t'System.CreatedBy': AzureUser;\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\nexport function fromAzureWorkItem(\n\tworkItem: WorkItem,\n\tprovider: Provider,\n\tproject: AzureProjectDescriptor,\n\tstateCategory?: AzureWorkItemStateCategory,\n\tprojection?: IssueProjection,\n): Issue {\n\tconst state = workItem.fields['System.State'];\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\tisClosedAzureWorkItemStateCategory(stateCategory),\n\t\tazureWorkItemsStateCategoryToState(stateCategory),\n\t\tfromAzureUserToMember(workItem.fields['System.CreatedBy'], 'issue'),\n\t\tworkItem.fields['System.AssignedTo'] != null\n\t\t\t? [fromAzureUserToMember(workItem.fields['System.AssignedTo'], 'issue')]\n\t\t\t: [],\n\t\tundefined,\n\t\tworkItem.fields['Microsoft.VSTS.Common.ClosedDate']\n\t\t\t? new Date(workItem.fields['Microsoft.VSTS.Common.ClosedDate'])\n\t\t\t: undefined,\n\t\tundefined,\n\t\tworkItem.fields['System.CommentCount'],\n\t\tundefined,\n\t\tworkItem.fields['System.Description'],\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);\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): 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\tconst description = fields['System.Description'];\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\tdescription: typeof description === 'string' ? description : null,\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\n/** An identity field, as provider-apis maps it: `name` from `uniqueName`, `username` from `displayName`. */\nfunction fromAzureWorkItemIdentity(value: unknown): ProviderAccount | null {\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`. */\nfunction 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.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 closed =\n\t\tcategory != null\n\t\t\t? isTerminalIssueStateCategory(category)\n\t\t\t: parseAzureWorkItemDate(fields?.['Microsoft.VSTS.Common.ClosedDate']) != null;\n\treturn {\n\t\tstate: closed ? 'closed' : 'opened',\n\t\tupdatedDate: updatedDate,\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":[],"sources":["../../../../../../plus/integrations/src/providers/azure/models.ts"],"sourcesContent":["import 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 { pullRequestRevision } from '../../models/integration.js';\nimport type { ProviderAccount, ProviderIssue } from '../models.js';\nimport {\n\tfromProviderPullRequestMergeableState,\n\tfromProviderPullRequestState,\n\tGitPullRequestMergeableState,\n\tGitPullRequestReviewState,\n\tGitPullRequestState,\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;\n\t\t'System.CreatedDate': string;\n\t\t'System.CreatedBy': AzureUser;\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\nexport function fromAzureWorkItem(\n\tworkItem: WorkItem,\n\tprovider: Provider,\n\tproject: AzureProjectDescriptor,\n\tstateCategory?: AzureWorkItemStateCategory,\n\tprojection?: IssueProjection,\n): Issue {\n\tconst state = workItem.fields['System.State'];\n\tconst closedDate = parseAzureWorkItemDate(workItem.fields['Microsoft.VSTS.Common.ClosedDate']) ?? undefined;\n\tconst normalizedState = azureWorkItemsStateCategoryToState(stateCategory, closedDate);\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\tfromAzureUserToMember(workItem.fields['System.CreatedBy'], 'issue'),\n\t\tworkItem.fields['System.AssignedTo'] != null\n\t\t\t? [fromAzureUserToMember(workItem.fields['System.AssignedTo'], 'issue')]\n\t\t\t: [],\n\t\tundefined,\n\t\tclosedDate,\n\t\tundefined,\n\t\tworkItem.fields['System.CommentCount'],\n\t\tundefined,\n\t\tworkItem.fields['System.Description'],\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);\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): 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\tconst description = fields['System.Description'];\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\tdescription: typeof description === 'string' ? description : null,\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\n/** An identity field, as provider-apis maps it: `name` from `uniqueName`, `username` from `displayName`. */\nfunction fromAzureWorkItemIdentity(value: unknown): ProviderAccount | null {\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.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};\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":";;;;;;;AA+BA,MAAa,qBAAqB;;;;;;AA4ClC,SAAgB,wBACf,UACiC;CACjC,QAAQ,UAAR;EACC,KAAK,WACJ,OAAO;EACR,KAAK;EACL,KAAK,YACJ,OAAO;EACR,KAAK,cACJ,OAAO;EACR,KAAK,YACJ,OAAO;EACR,SACC;CACF;AACD;AAEA,SAAgB,mCAAmC,UAA2D;CAC7G,OAAO,6BAA6B,wBAAwB,QAAQ,CAAC;AACtE;AAEA,SAAgB,mCACf,UACA,YACsC;CACtC,IAAI,wBAAwB,QAAQ,KAAK,MACxC,OAAO,mCAAmC,QAAQ,IAAI,WAAW;CAGlE,OAAO,cAAc,OAAO,WAAW,KAAA;AACxC;AA+FA,SAAgB,8BAA8B,QAAyD;CACtG,QAAQ,QAAR;EACC,KAAK,aACJ,OAAO;EACR,KAAK,aACJ,OAAO;EAGR,SACC,OAAO;CACT;AACD;AACA,SAAgB,+BAA+B,QAAyC;CACvF,OAAO,8BAA8B,MAAM,MAAM;AAClD;AA4MA,SAAgB,oBAAoB,KAAkB;CACrD,OAAO,IAAI,SAAS,MAAM,GAAG,CAAC,CAAC;AAChC;AACA,SAAgB,OAAO,QAAyB;CAC/C,OAAO,OAAO,YAAY,CAAC,CAAC,SAAS,kBAAkB;AACxD;;;;;AAMA,SAAgB,uBAAuB,OAAuB;CAC7D,IAAI,UAAU,OAAO,UAAU,MAAM,MAAM,IAAI,MAAM,+BAA+B,MAAM,GAAG;CAE7F,OAAO,mBAAmB,KAAK;AAChC;;;;;AAMA,SAAS,yBAAyB,SAAiB,OAAe,aAAqB,UAA0B;CAChH,MAAM,WAAW,GAAG,mBAAmB,KAAK,EAAE,GAAG,mBAAmB,WAAW,EAAE,QAAQ,mBAAmB,QAAQ;CACpH,OAAO,GAAG,QAAQ,QAAQ,QAAQ,EAAE,EAAE,GAAG;AAC1C;AAEA,SAAgB,0BAA0B,IAAsB,SAAiB,OAAuB;CAEvG,OAAO,GADS,yBAAyB,SAAS,OAAO,GAAG,WAAW,QAAQ,MAAM,GAAG,WAAW,IACnF,EAAE,eAAe,GAAG;AACrC;;;;;;AAOA,SAAS,6BAA6B,KAAU,UAAe,eAAgC;CAC9F,IAAI,IAAI,aAAa,SAAS,UAAU,OAAO;CAE/C,MAAM,QAAQ,cAAc,YAAY;CACxC,IAAI,SAAS,aAAa,mBAAmB,CAAC,OAAO,SAAS,QAAQ,GAAG,OAAO;CAEhF,IAAI,IAAI,aAAa,iBAAiB,OAAO,oBAAoB,GAAG,CAAC,CAAC,YAAY,MAAM;CAExF,OAAO,IAAI,aAAa,GAAG,QAAQ;AACpC;;;;;AAMA,SAAS,uBAAuB,KAAU,UAAe,eAAgC;CAExF,MAAM,SAAS,GADF,SAAS,SAAS,QAAQ,QAAQ,EAC1B,EAAE,GAAG,mBAAmB,aAAa,EAAE,GAAG,YAAY;CAC3E,OAAO,GAAG,IAAI,SAAS,GAAG,YAAY,CAAC,CAAC,WAAW,MAAM;AAC1D;;;;;;AAOA,SAAgB,2BACf,OACA,aACA,eACqB;CACrB,IAAI,SAAS,MAAM,OAAO,KAAA;CAE1B,IAAI;CACJ,IAAI;CACJ,IAAI;EACH,WAAW,IAAI,IAAI,WAAW;EAC9B,MAAM,IAAI,IAAI,KAAK;CACpB,QAAQ;EACP;CACD;CAEA,IAAI,SAAS,aAAa,YAAY,SAAS,aAAa,SAAS,OAAO,KAAA;CAC5E,IAAI,CAAC,eAAe,OAAO,KAAA;CAE3B,IAAI,IAAI,WAAW,SAAS,QACvB;MAAA,CAAC,uBAAuB,KAAK,UAAU,aAAa,GAAG,OAAO,KAAA;CAAA,OAC5D,IAAI,CAAC,6BAA6B,KAAK,UAAU,aAAa,GACpE;CAGD,IAAI,WAAW;CACf,IAAI,WAAW;CACf,IAAI,SAAS;CACb,IAAI,OAAO;CACX,OAAO,IAAI,SAAS;AACrB;;;;;;;;;;;;;;;;AAiBA,SAAS,2BACR,YACA,SACA,OACqB;CACrB,MAAM,YAAY,2BAA2B,WAAW,WAAW,SAAS,KAAK;CACjF,IAAI,aAAa,MAAM,OAAO,KAAA;CAG9B,MAAM,WAAW,IAAI,IAAI,SAAS,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,CAAC,QAAO,MAAK,EAAE,SAAS,CAAC;CAChF,MAAM,OAAO,SAAS,IAAI;CAC1B,IAAI,QAAQ,QAAQ,SAAS,IAAI,MAAM,QAAQ,OAAO,KAAA;CAEtD,IAAI;CACJ,IAAI;EACH,UAAU,mBAAmB,IAAI;CAClC,QAAQ;EACP;CACD;CAIA,OAAO,QAAQ,YAAY,MAAM,WAAW,KAAK,YAAY,IAAI,YAAY,KAAA;AAC9E;AAEA,SAAgB,gDACf,aAC4B;CAC5B,QAAQ,aAAR;EACC,KAAK,aACJ,OAAA;EACD,KAAK,WACJ,OAAA;EACD,KAAK,oBACJ,OAAA;EACD,KAAK,aACJ,OAAA;EAGD,SACC,OAAA;CACF;AACD;AAEA,SAAgB,sCAAsC,MAAoD;CACzG,QAAQ,MAAR;EACC,KAAK;EACL,KAAK,GACJ,OAAA;EACD,KAAK,GACJ,OAAA;EACD,KAAK;EACL,KAAK,KACJ,OAAA;EACD,SACC,OAAA;CACF;AACD;AAEA,SAAgB,gCAAgC,UAAkD;CACjG,OAAO;EACN,aAAa,KAAA;EACb,UAAU;GACT,WAAW,SAAS;GACpB,IAAI,SAAS;GACb,MAAM,SAAS;GACf,UAAU,SAAS;GACnB,KAAK,SAAS;EACf;EACA,OAAO,sCAAsC,SAAS,QAAQ,CAAC;CAChE;AACD;AAEA,SAAgB,kCACf,OACwC;CACxC,MAAM,eAAe,MAAM,KAAI,SAAQ,sCAAsC,IAAI,CAAC;CAClF,IAAI,aAAa,SAAA,mBAAgD,GAChE,OAAA;CAGD,IAAI,aAAa,SAAA,kBAA+C,GAC/D,OAAA;CAGD,IAAI,aAAa,SAAA,UAAwC,GACxD,OAAA;AAIF;AAEA,SAAgB,qCAAqC,UAAwC;CAC5F,OAAO;EACN,WAAW,SAAS;EACpB,IAAI,SAAS;EACb,MAAM,SAAS;EACf,UAAU,SAAS;EACnB,KAAK,SAAS;CACf;AACD;AAEA,SAAgB,yBAAyB,YAA4B;CACpE,OAAO,WAAW,WAAW,aAAa,IAAI,WAAW,QAAQ,eAAe,EAAE,IAAI;AACvF;AAIA,SAAS,sBAAsB,MAAiB,OAAiE;CAChH,OAAO;EACN,WAAW,KAAK;EAChB,IAAI,KAAK;EACT,MAAM,KAAK;EACX,UAAU,KAAK;EACf,KAAK,KAAK;CACX;AACD;;;;;AAoBA,SAAgB,qBACf,IACA,UACA,OACA,SACA,oBACA,YACc;CACd,MAAM,oBAAoB,yBAAyB,SAAS,OAAO,GAAG,WAAW,QAAQ,MAAM,GAAG,WAAW,IAAI;CACjH,MAAM,iBAAiB,2BAA2B,GAAG,YAAY,SAAS,KAAK;CAE/E,MAAM,iBAAiB,GAAG,YAAY;CACtC,MAAM,oBAAoB,kBAAkB,OAAO,oBAAoB,oBAAoB;CAC3F,MAAM,iBAAiB,kBAAkB,OAAO,iBAAiB,oBAAoB;CAErF,OAAO,IAAI,YACV,UACA,sBAAsB,GAAG,WAAW,aAAa,GACjD,GAAG,cAAc,SAAS,GAC1B,GAAG,cAAc,SAAS,GAC1B,GAAG,OACH,0BAA0B,IAAI,SAAS,KAAK,GAC5C;EACQ;EACP,MAAM,GAAG,WAAW;EACpB,IAAI,GAAG,WAAW;CACnB,GACA,8BAA8B,GAAG,MAAM,GACvC,IAAI,KAAK,GAAG,YAAY,GACxB,IAAI,KAAK,GAAG,cAAc,GAAG,YAAY,GACzC,GAAG,aAAa,IAAI,KAAK,GAAG,UAAU,IAAI,KAAA,GAC1C,GAAG,cAAc,GAAG,WAAW,cAAc,IAAI,KAAK,GAAG,UAAU,IAAI,KAAA,GAEvE,gDAAgD,GAAG,eAAe,QAAQ,GAC1E,KAAA,GACA;EACC,MAAM;GACL,QAAQ,GAAG,gBAAgB,yBAAyB,GAAG,aAAa,IAAI;GACxE,KAAK,GAAG,uBAAuB,YAAY;GAC3C,MAAM,GAAG,WAAW;GACb;GACP,QAAQ,GAAG,iBAAiB;GAC5B,KAAK;GACL,YAAY;EACb;EACA,MAAM;GACL,QAAQ,GAAG,gBAAgB,yBAAyB,GAAG,aAAa,IAAI;GACxE,KAAK,GAAG,uBAAuB,YAAY;GAC3C,MAAM,gBAAgB,QAAQ,GAAG,WAAW;GACrC;GACP,QAAQ,GAAG,iBAAiB;GAC5B,KAAK;GACL,YAAY;EACb;EACA,mBAAmB,GAAG,cAAc;CACrC,GACA,GAAG,SACH,KAAA,GACA,KAAA,GACA,KAAA,GACA,KAAA,GACA,kCAAkC,GAAG,WAAW,QAAO,MAAK,EAAE,UAAU,CAAC,CAAC,KAAI,MAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,GACrG,GAAG,UAAU,QAAO,MAAK,EAAE,QAAQ,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,KAAI,MAAK,gCAAgC,CAAC,CAAC,GACpG,GAAG,UAAU,QAAO,MAAK,EAAE,QAAQ,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,KAAI,MAAK,gCAAgC,CAAC,CAAC,GACpG,GAAG,UAAU,KAAI,MAAK,qCAAqC,CAAC,CAAC,GAC7D,KAAA,GACA;EACC,IAAI,GAAG,YAAY,SAAS;EAC5B,MAAM,GAAG,WAAW,QAAQ;EAC5B,YAAY;EACZ,cAAc;CACf,GACA,KAAA,GACA,KAAA,GACA,KAAA,GACA,KAAA,GACA,KAAA,GACA,KAAA,GACA,KAAA,GACA,UACD;AACD;AAEA,SAAgB,kBACf,UACA,UACA,SACA,eACA,YACQ;CACR,MAAM,QAAQ,SAAS,OAAO;CAC9B,MAAM,aAAa,uBAAuB,SAAS,OAAO,mCAAmC,KAAK,KAAA;CAClG,MAAM,kBAAkB,mCAAmC,eAAe,UAAU;CACpF,OAAO,IAAI,MACV,UACA,SAAS,GAAG,SAAS,GACrB,SAAS,GAAG,SAAS,GACrB,SAAS,OAAO,iBAChB,SAAS,OAAO,KAAK,MACrB,IAAI,KAAK,SAAS,OAAO,qBAAqB,GAC9C,IAAI,KAAK,SAAS,OAAO,qBAAqB,GAC9C,oBAAoB,UACpB,iBACA,sBAAsB,SAAS,OAAO,qBAAqB,OAAO,GAClE,SAAS,OAAO,wBAAwB,OACrC,CAAC,sBAAsB,SAAS,OAAO,sBAAsB,OAAO,CAAC,IACrE,CAAC,GACJ,KAAA,GACA,YACA,KAAA,GACA,SAAS,OAAO,wBAChB,KAAA,GACA,SAAS,OAAO,uBAChB,SACA,KAAA,GACA,SAAS,OAAO,wBAChB,OAAO,UAAU,YAAY,QAC1B;EAAE,MAAM;EAAO,UAAU,wBAAwB,aAAa;CAAE,IAChE,KAAA,GACH,KAAA,GACA,qBAAqB,SAAS,OAAO,uBAAuB,GAC5D,UACD;AACD;;;;;;;;;;AAWA,SAAS,qBAAqB,MAAwD;CACrF,IAAI,CAAC,MAAM,OAAO,KAAA;CAElB,MAAM,WAAW,KAAK,MAAM,IAAI;CAChC,IAAI,SAAS,SAAS,GAAG,OAAO,KAAA;CAEhC,MAAM,OAAO,SAAS,GAAG,EAAE,CAAC,EAAE,KAAK;CACnC,OAAO,OAAO,CAAC;EAAE,IAAI;EAAY;CAAK,CAAC,IAAI,KAAA;AAC5C;;;;;;;;;;;;AAgCA,SAAgB,iCACf,UACA,WACA,SAC4B;CAC5B,MAAM,SAAS,SAAS;CACxB,MAAM,KAAK,SAAS;CACpB,MAAM,QAAQ,SAAS;CACvB,MAAM,cAAc,uBAAuB,SAAS,qBAAqB;CACzE,IACC,UAAU,QACV,OAAO,OAAO,YACd,CAAC,OAAO,cAAc,EAAE,KACxB,MAAM,KACN,OAAO,UAAU,YACjB,CAAC,MAAM,KAAK,KACZ,eAAe,MAEf;CAGD,MAAM,WAAW,0BAA0B,OAAO,oBAAoB;CACtE,MAAM,eAAe,OAAO;CAC5B,MAAM,MAAM,SAAS,QAAQ,MAAM;CACnC,MAAM,OAAO,OAAO;CACpB,MAAM,QAAQ,OAAO;CACrB,MAAM,OAAO,OAAO;CACpB,MAAM,cAAc,OAAO;CAC3B,OAAO;EACN,IAAI,GAAG,SAAS;EAChB,QAAQ,GAAG,SAAS;EACb;EACP,cAAc,OAAO,iBAAiB,YAAY,OAAO,SAAS,YAAY,IAAI,eAAe;EACjG,QAAQ,0BAA0B,OAAO,mBAAmB;EAC5D,YAAY,uBAAuB,OAAO,mCAAmC;EAChE;EACb,aAAa,uBAAuB,OAAO,qBAAqB;EAChE,KAAK,OAAO,QAAQ,WAAW,MAAM;EACrC,WAAW,YAAY,OAAO,CAAC,QAAQ,IAAI,CAAC;EAC5C,aAAa,OAAO,gBAAgB,WAAW,cAAc;EAC7D,OAAO,OAAO,UAAU,YAAY,QAAQ;GAAE,MAAM;GAAO,OAAO;EAAK,IAAI;EAC3E,MAAM,OAAO,SAAS,WAAW,OAAO;EACxC,WAAW,yBAAyB,OAAO,uBAAuB;EAClE,YAAY;EACZ,SAAS;GAAa;GAAW,MAAM;GAAS,YAAY;GAAM,KAAK;GAAM,IAAI;EAAK;EAEtF,aAAa;EACb,SAAS,OAAO,SAAS,WAAW,KAAK,MAAM,GAAG,IAAI,CAAC,EAAA,CAAG,KAAI,SAAQ;GACrE,OAAO;GACP,aAAa;GACb,IAAI;GACJ,MAAM,IAAI,KAAK;EAChB,EAAE;CACH;AACD;;AAGA,SAAS,0BAA0B,OAAwC;CAC1E,MAAM,WAAW;CAIjB,IAAI,OAAO,UAAU,OAAO,YAAY,CAAC,SAAS,GAAG,KAAK,GAAG,OAAO;CAEpE,MAAM,YAAY,SAAS,QAAQ,QAAQ;CAC3C,OAAO;EACN,WAAW,OAAO,cAAc,WAAW,YAAY;EACvD,OAAO;EACP,IAAI,SAAS;EACb,MAAM,OAAO,SAAS,eAAe,WAAW,SAAS,aAAa;EACtE,UAAU,OAAO,SAAS,gBAAgB,WAAW,SAAS,cAAc;EAC5E,KAAK;CACN;AACD;;AAGA,SAAgB,uBAAuB,OAA6B;CACnE,IAAI,OAAO,UAAU,UAAU,OAAO;CAEtC,MAAM,QAAQ,mEAAmE,KAAK,KAAK;CAC3F,IAAI,SAAS,MAAM,OAAO;CAE1B,MAAM,CAAC,MAAM,OAAO,KAAK,MAAM,QAAQ,UAAU,MAAM,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM;CAE1E,MAAM,cAAc;EAAC;EADF,OAAO,MAAM,MAAM,OAAO,QAAQ,KAAK,OAAO,QAAQ,KACnC,KAAK;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;CAAE;CACrF,IAAI,QAAQ,KAAK,QAAQ,MAAM,MAAM,KAAK,MAAM,YAAY,QAAQ,MAAM,OAAO,MAAM,SAAS,MAAM,SAAS,IAC9G,OAAO;CAGR,MAAM,OAAO,IAAI,KAAK,KAAK;CAC3B,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,IAAI,OAAO;AAC9C;;AAGA,SAAS,yBAAyB,MAA2C;CAC5E,MAAM,CAAC,aAAa,qBAAqB,OAAO,SAAS,WAAW,OAAO,KAAA,CAAS,KAAK,CAAC;CAC1F,OAAO,aAAa,OAAO;EAAE,MAAM,UAAU;EAAI,MAAM,UAAU;CAAK,IAAI,KAAA;AAC3E;;;;;AAMA,MAAa,iCAAiC;;;;;;AAO9C,MAAa,8BAAiD;CAC7D;CACA;CACA;CACA;CACA;AACD;;;;;;;;AASA,SAAgB,0BACf,UACA,SACA,UACkB;CAClB,MAAM,SAAS,SAAS;CACxB,MAAM,cAAc,SAAS;CAC7B,IAAI,OAAO,gBAAgB,YAAY,YAAY,YAAY,MAAM,QAAQ,YAAY,GACxF,MAAM,IAAI,MACT,0BAA0B,OAAO,SAAS,EAAE,EAAE,kBAAkB,YAAY,UAAU,QAAQ,EAC/F;CAGD,MAAM,cAAc,uBAAuB,SAAS,qBAAqB;CACzE,IAAI,eAAe,MAClB,MAAM,IAAI,MAAM,mCAAmC,OAAO,SAAS,EAAE,EAAE,uBAAuB;CAW/F,OAAO;EACN,OARA,YAAY,OACT,6BAA6B,QAAQ,IACpC,WACA,WACD,uBAAuB,SAAS,mCAAmC,KAAK,OACvE,WACA,KAAA;EAGS;CACd;AACD;;AAWA,MAAM,iCAAoF;CACzF,QAAQ,oBAAoB;CAC5B,WAAW,oBAAoB;CAC/B,WAAW,oBAAoB;AAChC;;AAGA,MAAM,iCAAkF;CACvF,WAAW,6BAA6B;CACxC,SAAS,6BAA6B;CACtC,kBAAkB,6BAA6B;CAC/C,WAAW,6BAA6B;AACzC;;AAGA,MAAM,gCAA8E;CACnF,IAAI,0BAA0B;CAC9B,GAAG,0BAA0B;CAC7B,GAAG,0BAA0B;EAC5B,KAAK,0BAA0B;EAC/B,MAAM,0BAA0B;AAClC;;AAGA,SAAS,sBAAsB,UAAwD;CACtF,OAAO;EACN,UAAU;GACT,IAAI,SAAS;GACb,MAAM,SAAS,eAAe;GAC9B,WAAW,SAAS,cAAc,SAAS,gBAAgB;GAC3D,OAAO;GACP,WAAW,SAAS,YAAY;GAChC,KAAK;EACN;EACA,OAAO,2BAA2B,QAAQ;CAC3C;AACD;AAEA,SAAS,2BAA2B,UAAkD;CACrF,OAAO,8BAA8B,SAAS,QAAQ,MAAM,0BAA0B;AACvF;;;;;;;;;;;;;AAcA,SAAgB,6BACf,IACA,cACwB;CACxB,IACC,GAAG,iBAAiB,QACpB,GAAG,aAAa,QAChB,GAAG,YAAY,WAAW,QAC1B,GAAG,yBAAyB,QAC5B,GAAG,yBAAyB,QAC5B,CAAC,MAAM,QAAQ,GAAG,SAAS,KAC3B,GAAG,UAAU,MAAK,MAAK,KAAK,IAAI,KAChC,OAAO,GAAG,QAAQ,YAClB,OAAO,GAAG,kBAAkB,YAC5B,OAAO,GAAG,kBAAkB,UAE5B,MAAM,IAAI,MAAM,sCAAsC,OAAO,GAAG,aAAa,EAAE,yBAAyB;CAGzG,MAAM,UAAU,GAAG,UAAU,IAAI,qBAAqB;CACtD,MAAM,SAAgC;EAErC,OAAO,6BAA6B,+BAA+B,GAAG,OAAmC;EACzG,SAAS,GAAG;EACZ,aAAa,IAAI,KAAK,GAAG,cAAc,GAAG,YAAY;EACtD,SAAS,GAAG,sBAAsB,YAAY;EAC9C,UAAU,oBAAoB;GAC7B,OAAO,GAAG;GACV,MAAM,GAAG,eAAe,KAAA;GACxB,MAAM,EAAE,MAAM,EAAE,QAAQ,yBAAyB,GAAG,aAAa,EAAE,EAAE;GACrE,gBAAgB,iBAAiB,OAAO;GACxC,eAAe,mBAAmB,OAAO;EAC1C,CAAC;CACF;CAEA,IAAI,aAAa,SAAS,WAAW,GAAG;EACvC,MAAM,aACJ,GAAG,eAAe,OAAO,+BAA+B,GAAG,eAAe,KAAA,MAC3E,6BAA6B;EAC9B,OAAO,iBAAiB,sCAAsC;CAC/D;CAEA,IAAI,aAAa,SAAS,gBAAgB,GAAG;EAE5C,MAAM,WAAW,6BAChB,GAAG,UAAU,QAAO,MAAK,EAAE,UAAU,CAAC,CAAC,IAAI,0BAA0B,CACtE;EACA,OAAO,iBAAiB,WAAW,8BAA8B,YAAY,KAAA;CAC9E;CAEA,OAAO;AACR"}
@@ -1,6 +1,7 @@
1
1
  import { IssueSearchCriteria, IssueSearchRelationship, IssueSorting } from "../../../../git/models/issue.js";
2
2
  import { PullRequestSearchCriteria, PullRequestSorting } from "../../../../git/models/pullRequest.js";
3
3
  import { ProviderPullRequest } from "../models.js";
4
+ import { AzureWorkItemTypeStates } from "@gitkraken/provider-apis";
4
5
  //#region ../plus/integrations/src/providers/azure/search.d.ts
5
6
  /**
6
7
  * The most work items one filtered search can reach, however it is paged.
@@ -30,14 +31,15 @@ export declare const azureWorkItemSearchSorts: IssueSorting[];
30
31
  * The projects bound the query by name through `[System.TeamProject]`, so a repository scope reaches the work items
31
32
  * of its project; with no project the collection itself is the scope.
32
33
  *
33
- * The open/closed split mirrors the SDK's WIQL, so this search and the ordinary project read agree on which work
34
- * items are open. Free text is a `Contains` substring match on the title, and each label a `Contains` over the
35
- * tags, which Azure matches against whole tags — AND-ed, like the criteria model's labels.
34
+ * Workflow metadata selects state names per project and work item type: a name can belong to different categories
35
+ * in different workflows, and Removed work items need not have a closed date. Free text is a `Contains` substring
36
+ * match on the title, and each label a `Contains` over the tags, which Azure matches against whole tags, AND-ed like
37
+ * the criteria model's labels.
36
38
  *
37
39
  * Throws for a relationship or sort key it cannot express. The facade validates both against the declared
38
40
  * capability first, so reaching here with one means the declaration and this translation disagree.
39
41
  */
40
- export declare function toAzureWorkItemSearchWiql(criteria: IssueSearchCriteria | undefined, sort: IssueSorting, projects: readonly string[] | undefined): string;
42
+ export declare function toAzureWorkItemSearchWiql(criteria: IssueSearchCriteria | undefined, sort: IssueSorting, projects: readonly string[] | undefined, workflows?: ReadonlyMap<string, AzureWorkItemTypeStates>): string;
41
43
  /**
42
44
  * Why a search's dates can't be applied, or `undefined` when they can. The search checks this before any request,
43
45
  * so a date it would refuse costs nothing, and in a count refuses only its own scope.
@@ -43,7 +43,7 @@ const azureWorkItemSearchRelationships = Object.keys(relationshipClauses);
43
43
  * process-template fields. On an on-premises collection running the XML process model, a field its process never
44
44
  * declared fails the whole query with `TF51005` rather than ordering nothing, so declaring them would turn a
45
45
  * working search into a refused one on exactly the installations this search exists for. Every key kept orders a
46
- * system field, except `closed`, whose column the open/closed filter below already depends on.
46
+ * system field, except `closed`, which explicitly requests the process-template close-date field.
47
47
  */
48
48
  const sortColumns = {
49
49
  "updated:desc": "[System.ChangedDate] Desc",
@@ -67,21 +67,40 @@ const azureWorkItemSearchSorts = Object.keys(sortColumns);
67
67
  * The projects bound the query by name through `[System.TeamProject]`, so a repository scope reaches the work items
68
68
  * of its project; with no project the collection itself is the scope.
69
69
  *
70
- * The open/closed split mirrors the SDK's WIQL, so this search and the ordinary project read agree on which work
71
- * items are open. Free text is a `Contains` substring match on the title, and each label a `Contains` over the
72
- * tags, which Azure matches against whole tags — AND-ed, like the criteria model's labels.
70
+ * Workflow metadata selects state names per project and work item type: a name can belong to different categories
71
+ * in different workflows, and Removed work items need not have a closed date. Free text is a `Contains` substring
72
+ * match on the title, and each label a `Contains` over the tags, which Azure matches against whole tags, AND-ed like
73
+ * the criteria model's labels.
73
74
  *
74
75
  * Throws for a relationship or sort key it cannot express. The facade validates both against the declared
75
76
  * capability first, so reaching here with one means the declaration and this translation disagree.
76
77
  */
77
- function toAzureWorkItemSearchWiql(criteria, sort, projects) {
78
+ function toAzureWorkItemSearchWiql(criteria, sort, projects, workflows) {
78
79
  const clauses = [];
79
80
  if (projects?.length) clauses.push(`[System.TeamProject] IN (${projects.map((p) => `'${escapeWiqlString(p)}'`).join(", ")})`);
80
- switch (criteria?.state ?? "open") {
81
- case "open":
82
- clauses.push("[Microsoft.VSTS.Common.ClosedDate] = ''", "[Microsoft.VSTS.Common.ResolvedDate] = ''");
83
- break;
84
- case "closed": clauses.push("([Microsoft.VSTS.Common.ClosedDate] <> '' OR [Microsoft.VSTS.Common.ResolvedDate] <> '')");
81
+ const state = criteria?.state ?? "open";
82
+ if (state !== "all") {
83
+ if (workflows == null) throw new Error("Azure DevOps state-filtered search requires workflow metadata");
84
+ const states = [];
85
+ for (const [project, workflow] of workflows) {
86
+ if (workflow.metadata != null) throw new Error("Azure DevOps returned incomplete workflow metadata");
87
+ for (const type of workflow.types) {
88
+ if (type.states == null) throw new Error("Azure DevOps returned no work item states");
89
+ const names = type.states.filter((s) => {
90
+ switch (s.category) {
91
+ case "Completed":
92
+ case "Resolved":
93
+ case "Removed": return state === "closed";
94
+ case "Proposed":
95
+ case "InProgress": return state === "open";
96
+ default: throw new Error("Azure DevOps returned an unknown work item state category");
97
+ }
98
+ });
99
+ if (!names.length) continue;
100
+ states.push(`([System.TeamProject] = '${escapeWiqlString(project)}' AND [System.WorkItemType] = '${escapeWiqlString(type.name)}' AND [System.State] IN (${names.map((s) => `'${escapeWiqlString(s.name)}'`).join(", ")}))`);
101
+ }
102
+ }
103
+ clauses.push(states.length ? `(${states.join(" OR ")})` : "[System.Id] = 0");
85
104
  }
86
105
  const relationships = [...new Set(criteria?.relationships ?? [])];
87
106
  if (relationships.length > 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","names":[],"sources":["../../../../../../plus/integrations/src/providers/azure/search.ts"],"sourcesContent":["import { escapeWiqlString } from '@gitkraken/provider-apis';\nimport type { IssueSearchCriteria, IssueSearchRelationship, IssueSorting } from '@gitlens/git/models/issue.js';\nimport type { PullRequestSearchCriteria, PullRequestSorting } from '@gitlens/git/models/pullRequest.js';\nimport { fnv1aHash64 } from '@gitlens/utils/hash.js';\nimport type { ProviderPullRequest } from '../models.js';\nimport { parsePageCursor } from '../utils/providerPaging.js';\n\n/**\n * The most work items one filtered search can reach, however it is paged.\n *\n * Azure DevOps documents 20,000 work items as its query result limit, and may refuse a larger WIQL result set\n * (`VS402337`) rather than truncate it. The page read therefore asks for a window bounded by `$top`, the first\n * 20,000 matches under the requested order. Azure DevOps Server 2020 answers that bounded query with 20,014 matches\n * (it even answered one without `$top`), so the ceiling is this read's, kept at the documented limit.\n */\nexport const azureWorkItemSearchResultLimit = 20000;\n\n/** The work items one detail read can resolve: `workitemsbatch` rejects more than 200 ids. */\nexport const azureWorkItemBatchLimit = 200;\n\n/**\n * A search page size from a caller's `itemsPerPage`, within `[1, max]`. A value that isn't a finite number takes the\n * default: `NaN` would survive the clamp and serve empty pages that never advance.\n */\nexport function toAzureSearchPageSize(value: number | undefined, fallback: number, max: number): number {\n\tconst size = value != null && Number.isFinite(value) ? Math.trunc(value) : fallback;\n\treturn Math.min(max, Math.max(1, size));\n}\n\n/**\n * Each filtered-search relationship as the WIQL clause that expresses it.\n *\n * `@Me` rather than an identity looked up per read: Azure resolves the macro to the identity the credential\n * authenticates as, which is exactly the \"current user\" the relationship names, and needs no account read.\n * `mentioned` is absent. `@RecentMentions` only covers the last 30 days, so declaring it would serve an arbitrary\n * window under a relationship that promises every mention.\n */\nconst relationshipClauses: Partial<Record<IssueSearchRelationship, string>> = {\n\tauthored: '[System.CreatedBy] = @Me',\n\tassigned: '[System.AssignedTo] = @Me',\n\t'any-assignee': \"[System.AssignedTo] <> ''\",\n\tunassigned: \"[System.AssignedTo] = ''\",\n};\n\n/** The relationships {@link toAzureWorkItemSearchWiql} can express. */\nexport const azureWorkItemSearchRelationships = Object.keys(relationshipClauses) as IssueSearchRelationship[];\n\n/**\n * Each orderable key as its WIQL `ORDER BY` column.\n *\n * A strict subset of the SDK's WIQL columns: `priority` and `resolved` are left out because their columns are\n * process-template fields. On an on-premises collection running the XML process model, a field its process never\n * declared fails the whole query with `TF51005` rather than ordering nothing, so declaring them would turn a\n * working search into a refused one on exactly the installations this search exists for. Every key kept orders a\n * system field, except `closed`, whose column the open/closed filter below already depends on.\n */\nconst sortColumns: Partial<Record<IssueSorting, string>> = {\n\t'updated:desc': '[System.ChangedDate] Desc',\n\t'updated:asc': '[System.ChangedDate] Asc',\n\t'created:desc': '[System.CreatedDate] Desc',\n\t'created:asc': '[System.CreatedDate] Asc',\n\t'closed:desc': '[Microsoft.VSTS.Common.ClosedDate] Desc',\n\t'closed:asc': '[Microsoft.VSTS.Common.ClosedDate] Asc',\n\t'comments:desc': '[System.CommentCount] Desc',\n\t'comments:asc': '[System.CommentCount] Asc',\n\t'title:desc': '[System.Title] Desc',\n\t'title:asc': '[System.Title] Asc',\n};\n\n/** The sort keys {@link toAzureWorkItemSearchWiql} can express. */\nexport const azureWorkItemSearchSorts = Object.keys(sortColumns) as IssueSorting[];\n\n/**\n * Translates filtered-search criteria into ONE WIQL query over a collection.\n *\n * Relationships are OR-ed inside one parenthesized group rather than run as separate queries, which is what lets a\n * single query both page and count their union exactly: an item matching two relationships is one row, never two.\n * The projects bound the query by name through `[System.TeamProject]`, so a repository scope reaches the work items\n * of its project; with no project the collection itself is the scope.\n *\n * The open/closed split mirrors the SDK's WIQL, so this search and the ordinary project read agree on which work\n * items are open. Free text is a `Contains` substring match on the title, and each label a `Contains` over the\n * tags, which Azure matches against whole tags — AND-ed, like the criteria model's labels.\n *\n * Throws for a relationship or sort key it cannot express. The facade validates both against the declared\n * capability first, so reaching here with one means the declaration and this translation disagree.\n */\nexport function toAzureWorkItemSearchWiql(\n\tcriteria: IssueSearchCriteria | undefined,\n\tsort: IssueSorting,\n\tprojects: readonly string[] | undefined,\n): string {\n\tconst clauses: string[] = [];\n\tif (projects?.length) {\n\t\tclauses.push(`[System.TeamProject] IN (${projects.map(p => `'${escapeWiqlString(p)}'`).join(', ')})`);\n\t}\n\n\tswitch (criteria?.state ?? 'open') {\n\t\tcase 'open':\n\t\t\tclauses.push(\"[Microsoft.VSTS.Common.ClosedDate] = ''\", \"[Microsoft.VSTS.Common.ResolvedDate] = ''\");\n\t\t\tbreak;\n\t\tcase 'closed':\n\t\t\tclauses.push(\"([Microsoft.VSTS.Common.ClosedDate] <> '' OR [Microsoft.VSTS.Common.ResolvedDate] <> '')\");\n\t\t\tbreak;\n\t\tcase 'all':\n\t\t\tbreak;\n\t}\n\n\tconst relationships = [...new Set(criteria?.relationships ?? [])];\n\tif (relationships.length > 0) {\n\t\tconst expressions = relationships.map(r => {\n\t\t\tconst clause = relationshipClauses[r];\n\t\t\tif (clause == null) throw new Error(`Azure DevOps cannot search work items by the '${r}' relationship`);\n\n\t\t\treturn clause;\n\t\t});\n\t\tclauses.push(`(${expressions.join(' OR ')})`);\n\t}\n\n\tconst text = criteria?.text?.trim();\n\tif (text) {\n\t\tclauses.push(`[System.Title] Contains '${escapeWiqlString(text)}'`);\n\t}\n\tfor (const label of criteria?.labels ?? []) {\n\t\tclauses.push(`[System.Tags] Contains '${escapeWiqlString(label)}'`);\n\t}\n\tif (criteria?.updatedAfter != null) {\n\t\tclauses.push(`[System.ChangedDate] >= '${toWiqlDate(criteria.updatedAfter, 'updatedAfter')}'`);\n\t}\n\tif (criteria?.createdAfter != null) {\n\t\tclauses.push(`[System.CreatedDate] >= '${toWiqlDate(criteria.createdAfter, 'createdAfter')}'`);\n\t}\n\n\tconst column = sortColumns[sort];\n\tif (column == null) throw new Error(`Azure DevOps cannot order a work item search by '${sort}'`);\n\n\t// `[System.Id]` makes the order total, so a work item tying on the sort column can't cross a page boundary\n\t// between two requests and be served twice or never.\n\tconst where = clauses.length > 0 ? ` WHERE ${clauses.join(' AND ')}` : '';\n\treturn `SELECT [System.Id] FROM WorkItems${where} ORDER BY ${column}, [System.Id] Desc`;\n}\n\n/**\n * A criteria date as the UTC-midnight literal WIQL compares against. A literal with a time is only accepted by a\n * query sent with `timePrecision=true`, which is how the search sends every query: without it WIQL compares whole\n * days in the server's time zone, and the instant would differ from the one the pull request filter applies.\n */\nfunction toWiqlDate(value: string, name: string): string {\n\treturn parseCriteriaDate(value, name).toISOString().replace('.000Z', 'Z');\n}\n\n/**\n * A criteria date as UTC midnight.\n *\n * Validated to the `YYYY-MM-DD` the criteria model documents rather than parsed leniently: the WIQL value lands\n * inside a quoted literal, where any other shape is either refused or read in the server's locale, and the pull\n * request filter must apply the same instant the work-item query does.\n */\nfunction parseCriteriaDate(value: string, name: string): Date {\n\tconst date = toCriteriaDate(value);\n\tif (date == null) throw new Error(`Invalid ${name} '${value}'; expected an ISO date (YYYY-MM-DD)`);\n\n\treturn date;\n}\n\nfunction toCriteriaDate(value: string): Date | undefined {\n\tconst trimmed = value.trim();\n\tconst match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(trimmed);\n\tconst date = match != null ? new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]))) : null;\n\treturn date == null || Number.isNaN(date.getTime()) || date.toISOString().slice(0, 10) !== trimmed\n\t\t? undefined\n\t\t: date;\n}\n\n/**\n * Why a search's dates can't be applied, or `undefined` when they can. The search checks this before any request,\n * so a date it would refuse costs nothing, and in a count refuses only its own scope.\n */\nexport function getInvalidAzureSearchDate(\n\tcriteria: { updatedAfter?: string; createdAfter?: string } | undefined,\n): string | undefined {\n\tfor (const [name, value] of [\n\t\t['updatedAfter', criteria?.updatedAfter],\n\t\t['createdAfter', criteria?.createdAfter],\n\t] as const) {\n\t\tif (value != null && toCriteriaDate(value) == null) {\n\t\t\treturn `invalid ${name} '${value}'; expected an ISO date (YYYY-MM-DD)`;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * The criteria a pull-request search applies AFTER the provider read, as one predicate.\n *\n * Azure's pull request query takes a creator, a reviewer, a status and a repository, and nothing else, so text,\n * draft state and dates are matched here against each fully drained facet. Applying them to a complete facet is\n * exact rather than a post-filter of a page: nothing the predicate removes was ever counted toward a page or a\n * total, so a count and its list always agree.\n *\n * What each criterion matches is bounded by what the list endpoint returns, which is narrower than the web UI:\n * - text is a case-insensitive substring of the title or of the description — which Azure truncates to its first\n * 400 characters in a pull request list, so a term only found past that is not matched;\n * - `updatedAfter` compares Azure's `updatedDate`, which the SDK derives from the close date, or the creation date\n * for a pull request still open, since Azure reports no last-activity date. An old but active pull request is\n * therefore matched by its creation date.\n */\nexport function toAzurePullRequestSearchFilter(\n\tcriteria: PullRequestSearchCriteria | undefined,\n): (pr: ProviderPullRequest) => boolean {\n\tconst text = criteria?.text?.trim().toLowerCase();\n\tconst draft = criteria?.draft;\n\tconst updatedAfter =\n\t\tcriteria?.updatedAfter != null ? parseCriteriaDate(criteria.updatedAfter, 'updatedAfter') : undefined;\n\tconst createdAfter =\n\t\tcriteria?.createdAfter != null ? parseCriteriaDate(criteria.createdAfter, 'createdAfter') : undefined;\n\n\treturn pr =>\n\t\t(!text || pr.title.toLowerCase().includes(text) || (pr.description?.toLowerCase().includes(text) ?? false)) &&\n\t\t(draft == null || pr.isDraft === draft) &&\n\t\t(updatedAfter == null || pr.updatedDate >= updatedAfter) &&\n\t\t(createdAfter == null || pr.createdDate >= createdAfter);\n}\n\n/** The pull-request sort keys the search can order its merged facets by. */\nexport const azurePullRequestSearchSorts: PullRequestSorting[] = [\n\t'updated:desc',\n\t'updated:asc',\n\t'created:desc',\n\t'created:asc',\n];\n\n/**\n * A pull request's position in the merged search result: the sort date plus the identity that breaks ties.\n *\n * The identity makes the order TOTAL, which is what lets a cursor resume strictly after the last row served rather\n * than at a numeric offset. An offset would skip or repeat rows whenever a pull request changes state or date\n * between two pages; a keyset position only moves past rows that sort before it.\n */\nexport interface AzurePullRequestSearchPosition {\n\ttime: number;\n\tidentity: string;\n}\n\nexport function toAzurePullRequestSearchPosition(\n\tpr: ProviderPullRequest,\n\tsort: PullRequestSorting,\n\tidentity: string,\n): AzurePullRequestSearchPosition {\n\treturn { time: (sort.startsWith('created') ? pr.createdDate : pr.updatedDate).getTime(), identity: identity };\n}\n\n/** Orders two positions under `sort`, ties broken by identity so no two rows ever compare equal. */\nexport function compareAzurePullRequestSearchPositions(\n\ta: AzurePullRequestSearchPosition,\n\tb: AzurePullRequestSearchPosition,\n\tsort: PullRequestSorting,\n): number {\n\tif (a.time !== b.time) return sort.endsWith(':asc') ? a.time - b.time : b.time - a.time;\n\n\treturn a.identity < b.identity ? -1 : a.identity > b.identity ? 1 : 0;\n}\n\n/**\n * Where a filtered pull-request search resumes: after `after`, in the drain its first page read, under the query\n * `key` fingerprints. Bound to its query and served only from its own drain for the same reasons\n * {@link AzureWorkItemSearchCursor} is bound to its query and snapshot.\n */\nexport interface AzurePullRequestSearchCursor {\n\tkey: string;\n\tdrain: string;\n\tpage: number;\n\tafter: AzurePullRequestSearchPosition;\n}\n\n/**\n * The fingerprint {@link AzurePullRequestSearchCursor.key} carries for a query.\n *\n * It only guards a caller threading a cursor under a different query, and keeps the query itself out of the opaque\n * cursor. It never selects cached results: those are keyed by the full query (see the callers), so a fingerprint\n * collision can at worst accept a cursor, never serve another query's rows.\n */\nexport function toAzurePullRequestSearchCursorKey(query: unknown): string {\n\treturn fnv1aHash64(JSON.stringify(query));\n}\n\n/** Reads a caller-supplied cursor for the query fingerprinted as `key`; see {@link parseAzureWorkItemSearchCursor}. */\nexport function parseAzurePullRequestSearchCursor(\n\tcursor: string | undefined,\n\tkey: string,\n): AzurePullRequestSearchCursor | undefined {\n\tif (cursor == null) return undefined;\n\n\tlet parsed: Partial<AzurePullRequestSearchCursor> | undefined;\n\ttry {\n\t\tparsed = JSON.parse(cursor) as Partial<AzurePullRequestSearchCursor>;\n\t} catch {}\n\n\tconst after = parsed?.after;\n\tif (\n\t\tparsed?.key !== key ||\n\t\ttypeof parsed.drain !== 'string' ||\n\t\t!parsed.drain ||\n\t\t!Number.isSafeInteger(parsed.page) ||\n\t\tparsed.page! < 1 ||\n\t\ttypeof after?.time !== 'number' ||\n\t\t!Number.isFinite(after.time) ||\n\t\ttypeof after.identity !== 'string'\n\t) {\n\t\tthrow new Error(\n\t\t\t'Pull request search cursor was produced by a different query; restart the read without a cursor',\n\t\t);\n\t}\n\n\treturn { key: key, drain: parsed.drain, page: parsed.page!, after: { time: after.time, identity: after.identity } };\n}\n\n/**\n * Where a filtered work-item search resumes: an offset into the id snapshot its first page queried, the id of that\n * snapshot, and the fingerprint of the query that produced it.\n *\n * A continuation is only served from ITS snapshot, where the offset is exact. Each first page queries a snapshot of\n * its own, so a pagination of the same query started later never replaces the one an earlier pagination is reading.\n * The snapshot is kept for as long as the pagination keeps reading it; once it is gone the continuation is REFUSED\n * rather than resumed against a fresh query, since under an `updated` order a re-query can move an unseen work item\n * ahead of the offset, where it would be skipped without anything saying so. A cursor without a `snapshot` is the\n * facade's own page marker, which names a page by number with no snapshot behind it and is read against a fresh\n * query as exactly that.\n *\n * The fingerprint binds the cursor to its query (scope, criteria and order) without publishing any of them inside\n * the opaque cursor. A cursor for another query is REFUSED rather than silently restarted: this read is cursor-only,\n * so a restart would be served under the page number the caller supplied alongside it — page 1's rows labelled as\n * page N.\n */\nexport interface AzureWorkItemSearchCursor {\n\tkey: string;\n\tsnapshot?: string;\n\toffset: number;\n\tpage: number;\n}\n\n/** The fingerprint {@link AzureWorkItemSearchCursor.key} carries for a query; see {@link toAzurePullRequestSearchCursorKey}. */\nexport function toAzureWorkItemSearchCursorKey(collection: string, wiql: string): string {\n\treturn fnv1aHash64(JSON.stringify([collection, wiql]));\n}\n\n/**\n * Whether `cursor` starts a work-item search's first page: none at all, or the facade's page-1 marker, which\n * {@link parseAzureWorkItemSearchCursor} reads the same way.\n */\nexport function isAzureWorkItemSearchFirstPage(cursor: string | undefined): boolean {\n\treturn cursor == null || parsePageCursor(cursor) === 1;\n}\n\n/**\n * Reads a caller-supplied cursor for the query fingerprinted as `key`.\n *\n * Returns `undefined` when there is no cursor, and throws when there is one this query can't resume, since treating\n * a foreign or malformed cursor as page 1 would publish the first page's rows as the page the caller asked for.\n *\n * The one cursor it accepts without a fingerprint is the facade's own page marker (`{ value, type: 'page' }`), which\n * `broadenIssues` sends to retry or reach a numbered page it holds no cursor for. It names a position rather than a\n * query, so it is honored as the offset of that page at `pageSize` — page 1 being no cursor at all.\n */\nexport function parseAzureWorkItemSearchCursor(\n\tcursor: string | undefined,\n\tkey: string,\n\tpageSize: number,\n): AzureWorkItemSearchCursor | undefined {\n\tif (cursor == null) return undefined;\n\n\tconst pageMarker = parsePageCursor(cursor);\n\tif (pageMarker != null && Number.isSafeInteger(pageMarker) && pageMarker >= 1) {\n\t\treturn pageMarker === 1 ? undefined : { key: key, offset: (pageMarker - 1) * pageSize, page: pageMarker };\n\t}\n\n\tlet parsed: Partial<AzureWorkItemSearchCursor> | undefined;\n\ttry {\n\t\tparsed = JSON.parse(cursor) as Partial<AzureWorkItemSearchCursor>;\n\t} catch {}\n\n\tif (\n\t\tparsed?.key !== key ||\n\t\ttypeof parsed.snapshot !== 'string' ||\n\t\t!parsed.snapshot ||\n\t\t!Number.isSafeInteger(parsed.offset) ||\n\t\tparsed.offset! < 0 ||\n\t\t!Number.isSafeInteger(parsed.page) ||\n\t\tparsed.page! < 1\n\t) {\n\t\tthrow new Error('Work item search cursor was produced by a different query; restart the read without a cursor');\n\t}\n\n\treturn { key: key, snapshot: parsed.snapshot, offset: parsed.offset!, page: parsed.page! };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"search.js","names":[],"sources":["../../../../../../plus/integrations/src/providers/azure/search.ts"],"sourcesContent":["import type { AzureWorkItemTypeStates } from '@gitkraken/provider-apis';\nimport { escapeWiqlString } from '@gitkraken/provider-apis';\nimport type { IssueSearchCriteria, IssueSearchRelationship, IssueSorting } from '@gitlens/git/models/issue.js';\nimport type { PullRequestSearchCriteria, PullRequestSorting } from '@gitlens/git/models/pullRequest.js';\nimport { fnv1aHash64 } from '@gitlens/utils/hash.js';\nimport type { ProviderPullRequest } from '../models.js';\nimport { parsePageCursor } from '../utils/providerPaging.js';\n\n/**\n * The most work items one filtered search can reach, however it is paged.\n *\n * Azure DevOps documents 20,000 work items as its query result limit, and may refuse a larger WIQL result set\n * (`VS402337`) rather than truncate it. The page read therefore asks for a window bounded by `$top`, the first\n * 20,000 matches under the requested order. Azure DevOps Server 2020 answers that bounded query with 20,014 matches\n * (it even answered one without `$top`), so the ceiling is this read's, kept at the documented limit.\n */\nexport const azureWorkItemSearchResultLimit = 20000;\n\n/** The work items one detail read can resolve: `workitemsbatch` rejects more than 200 ids. */\nexport const azureWorkItemBatchLimit = 200;\n\n/**\n * A search page size from a caller's `itemsPerPage`, within `[1, max]`. A value that isn't a finite number takes the\n * default: `NaN` would survive the clamp and serve empty pages that never advance.\n */\nexport function toAzureSearchPageSize(value: number | undefined, fallback: number, max: number): number {\n\tconst size = value != null && Number.isFinite(value) ? Math.trunc(value) : fallback;\n\treturn Math.min(max, Math.max(1, size));\n}\n\n/**\n * Each filtered-search relationship as the WIQL clause that expresses it.\n *\n * `@Me` rather than an identity looked up per read: Azure resolves the macro to the identity the credential\n * authenticates as, which is exactly the \"current user\" the relationship names, and needs no account read.\n * `mentioned` is absent. `@RecentMentions` only covers the last 30 days, so declaring it would serve an arbitrary\n * window under a relationship that promises every mention.\n */\nconst relationshipClauses: Partial<Record<IssueSearchRelationship, string>> = {\n\tauthored: '[System.CreatedBy] = @Me',\n\tassigned: '[System.AssignedTo] = @Me',\n\t'any-assignee': \"[System.AssignedTo] <> ''\",\n\tunassigned: \"[System.AssignedTo] = ''\",\n};\n\n/** The relationships {@link toAzureWorkItemSearchWiql} can express. */\nexport const azureWorkItemSearchRelationships = Object.keys(relationshipClauses) as IssueSearchRelationship[];\n\n/**\n * Each orderable key as its WIQL `ORDER BY` column.\n *\n * A strict subset of the SDK's WIQL columns: `priority` and `resolved` are left out because their columns are\n * process-template fields. On an on-premises collection running the XML process model, a field its process never\n * declared fails the whole query with `TF51005` rather than ordering nothing, so declaring them would turn a\n * working search into a refused one on exactly the installations this search exists for. Every key kept orders a\n * system field, except `closed`, which explicitly requests the process-template close-date field.\n */\nconst sortColumns: Partial<Record<IssueSorting, string>> = {\n\t'updated:desc': '[System.ChangedDate] Desc',\n\t'updated:asc': '[System.ChangedDate] Asc',\n\t'created:desc': '[System.CreatedDate] Desc',\n\t'created:asc': '[System.CreatedDate] Asc',\n\t'closed:desc': '[Microsoft.VSTS.Common.ClosedDate] Desc',\n\t'closed:asc': '[Microsoft.VSTS.Common.ClosedDate] Asc',\n\t'comments:desc': '[System.CommentCount] Desc',\n\t'comments:asc': '[System.CommentCount] Asc',\n\t'title:desc': '[System.Title] Desc',\n\t'title:asc': '[System.Title] Asc',\n};\n\n/** The sort keys {@link toAzureWorkItemSearchWiql} can express. */\nexport const azureWorkItemSearchSorts = Object.keys(sortColumns) as IssueSorting[];\n\n/**\n * Translates filtered-search criteria into ONE WIQL query over a collection.\n *\n * Relationships are OR-ed inside one parenthesized group rather than run as separate queries, which is what lets a\n * single query both page and count their union exactly: an item matching two relationships is one row, never two.\n * The projects bound the query by name through `[System.TeamProject]`, so a repository scope reaches the work items\n * of its project; with no project the collection itself is the scope.\n *\n * Workflow metadata selects state names per project and work item type: a name can belong to different categories\n * in different workflows, and Removed work items need not have a closed date. Free text is a `Contains` substring\n * match on the title, and each label a `Contains` over the tags, which Azure matches against whole tags, AND-ed like\n * the criteria model's labels.\n *\n * Throws for a relationship or sort key it cannot express. The facade validates both against the declared\n * capability first, so reaching here with one means the declaration and this translation disagree.\n */\nexport function toAzureWorkItemSearchWiql(\n\tcriteria: IssueSearchCriteria | undefined,\n\tsort: IssueSorting,\n\tprojects: readonly string[] | undefined,\n\tworkflows?: ReadonlyMap<string, AzureWorkItemTypeStates>,\n): string {\n\tconst clauses: string[] = [];\n\tif (projects?.length) {\n\t\tclauses.push(`[System.TeamProject] IN (${projects.map(p => `'${escapeWiqlString(p)}'`).join(', ')})`);\n\t}\n\n\tconst state = criteria?.state ?? 'open';\n\tif (state !== 'all') {\n\t\tif (workflows == null) throw new Error('Azure DevOps state-filtered search requires workflow metadata');\n\n\t\tconst states: string[] = [];\n\t\tfor (const [project, workflow] of workflows) {\n\t\t\tif (workflow.metadata != null) throw new Error('Azure DevOps returned incomplete workflow metadata');\n\n\t\t\tfor (const type of workflow.types) {\n\t\t\t\tif (type.states == null) throw new Error('Azure DevOps returned no work item states');\n\n\t\t\t\tconst names = type.states.filter(s => {\n\t\t\t\t\tswitch (s.category) {\n\t\t\t\t\t\tcase 'Completed':\n\t\t\t\t\t\tcase 'Resolved':\n\t\t\t\t\t\tcase 'Removed':\n\t\t\t\t\t\t\treturn state === 'closed';\n\t\t\t\t\t\tcase 'Proposed':\n\t\t\t\t\t\tcase 'InProgress':\n\t\t\t\t\t\t\treturn state === 'open';\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tthrow new Error('Azure DevOps returned an unknown work item state category');\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (!names.length) continue;\n\n\t\t\t\tstates.push(\n\t\t\t\t\t`([System.TeamProject] = '${escapeWiqlString(project)}' AND [System.WorkItemType] = '${escapeWiqlString(type.name)}' AND [System.State] IN (${names.map(s => `'${escapeWiqlString(s.name)}'`).join(', ')}))`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tclauses.push(states.length ? `(${states.join(' OR ')})` : '[System.Id] = 0');\n\t}\n\n\tconst relationships = [...new Set(criteria?.relationships ?? [])];\n\tif (relationships.length > 0) {\n\t\tconst expressions = relationships.map(r => {\n\t\t\tconst clause = relationshipClauses[r];\n\t\t\tif (clause == null) throw new Error(`Azure DevOps cannot search work items by the '${r}' relationship`);\n\n\t\t\treturn clause;\n\t\t});\n\t\tclauses.push(`(${expressions.join(' OR ')})`);\n\t}\n\n\tconst text = criteria?.text?.trim();\n\tif (text) {\n\t\tclauses.push(`[System.Title] Contains '${escapeWiqlString(text)}'`);\n\t}\n\tfor (const label of criteria?.labels ?? []) {\n\t\tclauses.push(`[System.Tags] Contains '${escapeWiqlString(label)}'`);\n\t}\n\tif (criteria?.updatedAfter != null) {\n\t\tclauses.push(`[System.ChangedDate] >= '${toWiqlDate(criteria.updatedAfter, 'updatedAfter')}'`);\n\t}\n\tif (criteria?.createdAfter != null) {\n\t\tclauses.push(`[System.CreatedDate] >= '${toWiqlDate(criteria.createdAfter, 'createdAfter')}'`);\n\t}\n\n\tconst column = sortColumns[sort];\n\tif (column == null) throw new Error(`Azure DevOps cannot order a work item search by '${sort}'`);\n\n\t// `[System.Id]` makes the order total, so a work item tying on the sort column can't cross a page boundary\n\t// between two requests and be served twice or never.\n\tconst where = clauses.length > 0 ? ` WHERE ${clauses.join(' AND ')}` : '';\n\treturn `SELECT [System.Id] FROM WorkItems${where} ORDER BY ${column}, [System.Id] Desc`;\n}\n\n/**\n * A criteria date as the UTC-midnight literal WIQL compares against. A literal with a time is only accepted by a\n * query sent with `timePrecision=true`, which is how the search sends every query: without it WIQL compares whole\n * days in the server's time zone, and the instant would differ from the one the pull request filter applies.\n */\nfunction toWiqlDate(value: string, name: string): string {\n\treturn parseCriteriaDate(value, name).toISOString().replace('.000Z', 'Z');\n}\n\n/**\n * A criteria date as UTC midnight.\n *\n * Validated to the `YYYY-MM-DD` the criteria model documents rather than parsed leniently: the WIQL value lands\n * inside a quoted literal, where any other shape is either refused or read in the server's locale, and the pull\n * request filter must apply the same instant the work-item query does.\n */\nfunction parseCriteriaDate(value: string, name: string): Date {\n\tconst date = toCriteriaDate(value);\n\tif (date == null) throw new Error(`Invalid ${name} '${value}'; expected an ISO date (YYYY-MM-DD)`);\n\n\treturn date;\n}\n\nfunction toCriteriaDate(value: string): Date | undefined {\n\tconst trimmed = value.trim();\n\tconst match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(trimmed);\n\tconst date = match != null ? new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]))) : null;\n\treturn date == null || Number.isNaN(date.getTime()) || date.toISOString().slice(0, 10) !== trimmed\n\t\t? undefined\n\t\t: date;\n}\n\n/**\n * Why a search's dates can't be applied, or `undefined` when they can. The search checks this before any request,\n * so a date it would refuse costs nothing, and in a count refuses only its own scope.\n */\nexport function getInvalidAzureSearchDate(\n\tcriteria: { updatedAfter?: string; createdAfter?: string } | undefined,\n): string | undefined {\n\tfor (const [name, value] of [\n\t\t['updatedAfter', criteria?.updatedAfter],\n\t\t['createdAfter', criteria?.createdAfter],\n\t] as const) {\n\t\tif (value != null && toCriteriaDate(value) == null) {\n\t\t\treturn `invalid ${name} '${value}'; expected an ISO date (YYYY-MM-DD)`;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * The criteria a pull-request search applies AFTER the provider read, as one predicate.\n *\n * Azure's pull request query takes a creator, a reviewer, a status and a repository, and nothing else, so text,\n * draft state and dates are matched here against each fully drained facet. Applying them to a complete facet is\n * exact rather than a post-filter of a page: nothing the predicate removes was ever counted toward a page or a\n * total, so a count and its list always agree.\n *\n * What each criterion matches is bounded by what the list endpoint returns, which is narrower than the web UI:\n * - text is a case-insensitive substring of the title or of the description — which Azure truncates to its first\n * 400 characters in a pull request list, so a term only found past that is not matched;\n * - `updatedAfter` compares Azure's `updatedDate`, which the SDK derives from the close date, or the creation date\n * for a pull request still open, since Azure reports no last-activity date. An old but active pull request is\n * therefore matched by its creation date.\n */\nexport function toAzurePullRequestSearchFilter(\n\tcriteria: PullRequestSearchCriteria | undefined,\n): (pr: ProviderPullRequest) => boolean {\n\tconst text = criteria?.text?.trim().toLowerCase();\n\tconst draft = criteria?.draft;\n\tconst updatedAfter =\n\t\tcriteria?.updatedAfter != null ? parseCriteriaDate(criteria.updatedAfter, 'updatedAfter') : undefined;\n\tconst createdAfter =\n\t\tcriteria?.createdAfter != null ? parseCriteriaDate(criteria.createdAfter, 'createdAfter') : undefined;\n\n\treturn pr =>\n\t\t(!text || pr.title.toLowerCase().includes(text) || (pr.description?.toLowerCase().includes(text) ?? false)) &&\n\t\t(draft == null || pr.isDraft === draft) &&\n\t\t(updatedAfter == null || pr.updatedDate >= updatedAfter) &&\n\t\t(createdAfter == null || pr.createdDate >= createdAfter);\n}\n\n/** The pull-request sort keys the search can order its merged facets by. */\nexport const azurePullRequestSearchSorts: PullRequestSorting[] = [\n\t'updated:desc',\n\t'updated:asc',\n\t'created:desc',\n\t'created:asc',\n];\n\n/**\n * A pull request's position in the merged search result: the sort date plus the identity that breaks ties.\n *\n * The identity makes the order TOTAL, which is what lets a cursor resume strictly after the last row served rather\n * than at a numeric offset. An offset would skip or repeat rows whenever a pull request changes state or date\n * between two pages; a keyset position only moves past rows that sort before it.\n */\nexport interface AzurePullRequestSearchPosition {\n\ttime: number;\n\tidentity: string;\n}\n\nexport function toAzurePullRequestSearchPosition(\n\tpr: ProviderPullRequest,\n\tsort: PullRequestSorting,\n\tidentity: string,\n): AzurePullRequestSearchPosition {\n\treturn { time: (sort.startsWith('created') ? pr.createdDate : pr.updatedDate).getTime(), identity: identity };\n}\n\n/** Orders two positions under `sort`, ties broken by identity so no two rows ever compare equal. */\nexport function compareAzurePullRequestSearchPositions(\n\ta: AzurePullRequestSearchPosition,\n\tb: AzurePullRequestSearchPosition,\n\tsort: PullRequestSorting,\n): number {\n\tif (a.time !== b.time) return sort.endsWith(':asc') ? a.time - b.time : b.time - a.time;\n\n\treturn a.identity < b.identity ? -1 : a.identity > b.identity ? 1 : 0;\n}\n\n/**\n * Where a filtered pull-request search resumes: after `after`, in the drain its first page read, under the query\n * `key` fingerprints. Bound to its query and served only from its own drain for the same reasons\n * {@link AzureWorkItemSearchCursor} is bound to its query and snapshot.\n */\nexport interface AzurePullRequestSearchCursor {\n\tkey: string;\n\tdrain: string;\n\tpage: number;\n\tafter: AzurePullRequestSearchPosition;\n}\n\n/**\n * The fingerprint {@link AzurePullRequestSearchCursor.key} carries for a query.\n *\n * It only guards a caller threading a cursor under a different query, and keeps the query itself out of the opaque\n * cursor. It never selects cached results: those are keyed by the full query (see the callers), so a fingerprint\n * collision can at worst accept a cursor, never serve another query's rows.\n */\nexport function toAzurePullRequestSearchCursorKey(query: unknown): string {\n\treturn fnv1aHash64(JSON.stringify(query));\n}\n\n/** Reads a caller-supplied cursor for the query fingerprinted as `key`; see {@link parseAzureWorkItemSearchCursor}. */\nexport function parseAzurePullRequestSearchCursor(\n\tcursor: string | undefined,\n\tkey: string,\n): AzurePullRequestSearchCursor | undefined {\n\tif (cursor == null) return undefined;\n\n\tlet parsed: Partial<AzurePullRequestSearchCursor> | undefined;\n\ttry {\n\t\tparsed = JSON.parse(cursor) as Partial<AzurePullRequestSearchCursor>;\n\t} catch {}\n\n\tconst after = parsed?.after;\n\tif (\n\t\tparsed?.key !== key ||\n\t\ttypeof parsed.drain !== 'string' ||\n\t\t!parsed.drain ||\n\t\t!Number.isSafeInteger(parsed.page) ||\n\t\tparsed.page! < 1 ||\n\t\ttypeof after?.time !== 'number' ||\n\t\t!Number.isFinite(after.time) ||\n\t\ttypeof after.identity !== 'string'\n\t) {\n\t\tthrow new Error(\n\t\t\t'Pull request search cursor was produced by a different query; restart the read without a cursor',\n\t\t);\n\t}\n\n\treturn { key: key, drain: parsed.drain, page: parsed.page!, after: { time: after.time, identity: after.identity } };\n}\n\n/**\n * Where a filtered work-item search resumes: an offset into the id snapshot its first page queried, the id of that\n * snapshot, and the fingerprint of the query that produced it.\n *\n * A continuation is only served from ITS snapshot, where the offset is exact. Each first page queries a snapshot of\n * its own, so a pagination of the same query started later never replaces the one an earlier pagination is reading.\n * The snapshot is kept for as long as the pagination keeps reading it; once it is gone the continuation is REFUSED\n * rather than resumed against a fresh query, since under an `updated` order a re-query can move an unseen work item\n * ahead of the offset, where it would be skipped without anything saying so. A cursor without a `snapshot` is the\n * facade's own page marker, which names a page by number with no snapshot behind it and is read against a fresh\n * query as exactly that.\n *\n * The fingerprint binds the cursor to its query (scope, criteria and order) without publishing any of them inside\n * the opaque cursor. A cursor for another query is REFUSED rather than silently restarted: this read is cursor-only,\n * so a restart would be served under the page number the caller supplied alongside it — page 1's rows labelled as\n * page N.\n */\nexport interface AzureWorkItemSearchCursor {\n\tkey: string;\n\tsnapshot?: string;\n\toffset: number;\n\tpage: number;\n}\n\n/** The fingerprint {@link AzureWorkItemSearchCursor.key} carries for a query; see {@link toAzurePullRequestSearchCursorKey}. */\nexport function toAzureWorkItemSearchCursorKey(collection: string, wiql: string): string {\n\treturn fnv1aHash64(JSON.stringify([collection, wiql]));\n}\n\n/**\n * Whether `cursor` starts a work-item search's first page: none at all, or the facade's page-1 marker, which\n * {@link parseAzureWorkItemSearchCursor} reads the same way.\n */\nexport function isAzureWorkItemSearchFirstPage(cursor: string | undefined): boolean {\n\treturn cursor == null || parsePageCursor(cursor) === 1;\n}\n\n/**\n * Reads a caller-supplied cursor for the query fingerprinted as `key`.\n *\n * Returns `undefined` when there is no cursor, and throws when there is one this query can't resume, since treating\n * a foreign or malformed cursor as page 1 would publish the first page's rows as the page the caller asked for.\n *\n * The one cursor it accepts without a fingerprint is the facade's own page marker (`{ value, type: 'page' }`), which\n * `broadenIssues` sends to retry or reach a numbered page it holds no cursor for. It names a position rather than a\n * query, so it is honored as the offset of that page at `pageSize` — page 1 being no cursor at all.\n */\nexport function parseAzureWorkItemSearchCursor(\n\tcursor: string | undefined,\n\tkey: string,\n\tpageSize: number,\n): AzureWorkItemSearchCursor | undefined {\n\tif (cursor == null) return undefined;\n\n\tconst pageMarker = parsePageCursor(cursor);\n\tif (pageMarker != null && Number.isSafeInteger(pageMarker) && pageMarker >= 1) {\n\t\treturn pageMarker === 1 ? undefined : { key: key, offset: (pageMarker - 1) * pageSize, page: pageMarker };\n\t}\n\n\tlet parsed: Partial<AzureWorkItemSearchCursor> | undefined;\n\ttry {\n\t\tparsed = JSON.parse(cursor) as Partial<AzureWorkItemSearchCursor>;\n\t} catch {}\n\n\tif (\n\t\tparsed?.key !== key ||\n\t\ttypeof parsed.snapshot !== 'string' ||\n\t\t!parsed.snapshot ||\n\t\t!Number.isSafeInteger(parsed.offset) ||\n\t\tparsed.offset! < 0 ||\n\t\t!Number.isSafeInteger(parsed.page) ||\n\t\tparsed.page! < 1\n\t) {\n\t\tthrow new Error('Work item search cursor was produced by a different query; restart the read without a cursor');\n\t}\n\n\treturn { key: key, snapshot: parsed.snapshot, offset: parsed.offset!, page: parsed.page! };\n}\n"],"mappings":"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"}
@@ -293,7 +293,7 @@ var AzureDevOpsServerIntegration = class extends AzureDevOpsIntegrationBase {
293
293
  return azure.searchWorkItemsPage(this, toTokenWithInfo(this.id, session), scope.collection, {
294
294
  baseUrl: this.collectionApiBaseUrl(session, scope.collection),
295
295
  criteria: options.criteria,
296
- projectNames: scope.projectNames,
296
+ projectNames: scope.projectNames ?? scope.projects.map((p) => p.name),
297
297
  resolveProject: (name) => scope.projects.find((p) => sameAzureName(p.name, name)),
298
298
  cursor: options.cursor,
299
299
  pageSize: options.pageSize
@@ -316,7 +316,7 @@ var AzureDevOpsServerIntegration = class extends AzureDevOpsIntegrationBase {
316
316
  return azure.countWorkItems(this, tokenWithInfo, scope.collection, {
317
317
  baseUrl: this.collectionApiBaseUrl(session, scope.collection),
318
318
  criteria: s.criteria,
319
- projectNames: scope.projectNames
319
+ projectNames: scope.projectNames ?? scope.projects.map((p) => p.name)
320
320
  }, cancellation);
321
321
  }));
322
322
  }