@gitkraken/core-gitlens 0.9.4 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -1
- package/dist/git/models/issue.d.ts +25 -2
- package/dist/git/models/issue.js +3 -1
- package/dist/git/models/issue.js.map +1 -1
- package/dist/git/models/pullRequest.d.ts +55 -5
- package/dist/git/models/pullRequest.js +5 -1
- package/dist/git/models/pullRequest.js.map +1 -1
- package/dist/git/utils/issue.utils.d.ts +4 -0
- package/dist/git/utils/issue.utils.js +4 -0
- package/dist/git/utils/issue.utils.js.map +1 -1
- package/dist/git/utils/pullRequest.utils.d.ts +4 -0
- package/dist/git/utils/pullRequest.utils.js +8 -0
- package/dist/git/utils/pullRequest.utils.js.map +1 -1
- package/dist/plus/git-github/api/github.d.ts +64 -4
- package/dist/plus/git-github/api/github.js +232 -15
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/git-github/models.d.ts +48 -6
- package/dist/plus/git-github/models.js +11 -10
- package/dist/plus/git-github/models.js.map +1 -1
- package/dist/plus/integrations/fieldPresence.d.ts +75 -0
- package/dist/plus/integrations/fieldPresence.js +381 -0
- package/dist/plus/integrations/fieldPresence.js.map +1 -0
- package/dist/plus/integrations/index.d.ts +6 -4
- package/dist/plus/integrations/index.js +2 -1
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +7 -3
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +56 -6
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +103 -9
- package/dist/plus/integrations/models/gitHostIntegration.js +116 -5
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +91 -4
- package/dist/plus/integrations/models/integration.js +56 -7
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/models/issuesIntegration.d.ts +44 -3
- package/dist/plus/integrations/models/issuesIntegration.js +51 -1
- package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.js +3 -3
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts +36 -4
- package/dist/plus/integrations/providers/azure/models.js +118 -8
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts +102 -5
- package/dist/plus/integrations/providers/azureDevOps.js +264 -45
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +30 -6
- package/dist/plus/integrations/providers/azureDevOpsServer.js +82 -21
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +21 -1
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js +74 -10
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket/models.d.ts +40 -3
- package/dist/plus/integrations/providers/bitbucket/models.js +30 -7
- package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server/models.d.ts +2 -6
- package/dist/plus/integrations/providers/bitbucket-server/models.js +1 -3
- package/dist/plus/integrations/providers/bitbucket-server/models.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.d.ts +3 -1
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js +4 -1
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server.d.ts +2 -2
- package/dist/plus/integrations/providers/bitbucket-server.js +7 -5
- package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket.d.ts +22 -2
- package/dist/plus/integrations/providers/bitbucket.js +29 -6
- package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/github.d.ts +40 -3
- package/dist/plus/integrations/providers/github.js +133 -11
- package/dist/plus/integrations/providers/github.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +34 -1
- package/dist/plus/integrations/providers/gitlab/gitlab.js +118 -4
- package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab/models.d.ts +70 -5
- package/dist/plus/integrations/providers/gitlab/models.js +106 -13
- package/dist/plus/integrations/providers/gitlab/models.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.d.ts +31 -2
- package/dist/plus/integrations/providers/gitlab.js +48 -8
- package/dist/plus/integrations/providers/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +1 -0
- package/dist/plus/integrations/providers/jira-server.d.ts +1 -0
- package/dist/plus/integrations/providers/jira-server.js +9 -6
- package/dist/plus/integrations/providers/jira-server.js.map +1 -1
- package/dist/plus/integrations/providers/jira.d.ts +20 -1
- package/dist/plus/integrations/providers/jira.js +101 -6
- package/dist/plus/integrations/providers/jira.js.map +1 -1
- package/dist/plus/integrations/providers/jiraIssueByKey.d.ts +79 -0
- package/dist/plus/integrations/providers/jiraIssueByKey.js +58 -7
- package/dist/plus/integrations/providers/jiraIssueByKey.js.map +1 -1
- package/dist/plus/integrations/providers/linear.d.ts +19 -1
- package/dist/plus/integrations/providers/linear.js +97 -5
- package/dist/plus/integrations/providers/linear.js.map +1 -1
- package/dist/plus/integrations/providers/linearIssuesEtag.d.ts +42 -0
- package/dist/plus/integrations/providers/linearIssuesEtag.js +94 -0
- package/dist/plus/integrations/providers/linearIssuesEtag.js.map +1 -0
- package/dist/plus/integrations/providers/models.d.ts +51 -7
- package/dist/plus/integrations/providers/models.js +45 -16
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providerErrors.js +8 -1
- package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +62 -3
- package/dist/plus/integrations/providers/providersApi.js +145 -4
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/pullRequestReviews.d.ts +13 -0
- package/dist/plus/integrations/providers/pullRequestReviews.js +30 -6
- package/dist/plus/integrations/providers/pullRequestReviews.js.map +1 -1
- package/dist/plus/integrations/providers/trello.js +10 -7
- package/dist/plus/integrations/providers/trello.js.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.d.ts +11 -0
- package/dist/plus/integrations/providers/utils/providerPaging.js +47 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
- package/dist/plus/integrations/providers/utils.d.ts +1 -1
- package/dist/plus/integrations/reads/batchEtags.d.ts +69 -0
- package/dist/plus/integrations/reads/batchEtags.js +114 -0
- package/dist/plus/integrations/reads/batchEtags.js.map +1 -0
- package/dist/plus/integrations/reads/counts.js +2 -2
- package/dist/plus/integrations/reads/counts.js.map +1 -1
- package/dist/plus/integrations/reads/currentAccount.js +2 -2
- package/dist/plus/integrations/reads/currentAccount.js.map +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +13 -3
- package/dist/plus/integrations/reads/drains.js +29 -5
- package/dist/plus/integrations/reads/drains.js.map +1 -1
- package/dist/plus/integrations/reads/etag.d.ts +26 -0
- package/dist/plus/integrations/reads/etag.js +108 -0
- package/dist/plus/integrations/reads/etag.js.map +1 -0
- package/dist/plus/integrations/reads/hierarchy.js +3 -3
- package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts +47 -16
- package/dist/plus/integrations/reads/issueBatch.js +55 -87
- package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.d.ts +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +4 -4
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/issues.d.ts +1 -1
- package/dist/plus/integrations/reads/issues.js +4 -4
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequestBatch.d.ts +44 -6
- package/dist/plus/integrations/reads/pullRequestBatch.js +31 -34
- package/dist/plus/integrations/reads/pullRequestBatch.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequestBranches.d.ts +5 -2
- package/dist/plus/integrations/reads/pullRequestBranches.js +4 -3
- package/dist/plus/integrations/reads/pullRequestBranches.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequests.js +8 -4
- package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
- package/dist/plus/integrations/reads/sweeps.js +8 -4
- package/dist/plus/integrations/reads/sweeps.js.map +1 -1
- package/dist/plus/integrations/reads/warnings.d.ts +23 -8
- package/dist/plus/integrations/reads/warnings.js +43 -19
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +12 -4
- package/dist/plus/integrations/results.js +2 -0
- package/dist/plus/integrations/results.js.map +1 -1
- package/docs/integrations.md +385 -23
- package/docs/kepler-read-api-parity.md +16 -2
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providerErrors.js","names":[],"sources":["../../../../../plus/integrations/src/providers/providerErrors.ts"],"sourcesContent":["import type { TokenWithInfo } from '../authentication/models.js';\nimport type { IntegrationIds } from '../constants.js';\nimport {\n\tGitCloudHostIntegrationId,\n\tGitSelfManagedHostIntegrationId,\n\tIssuesCloudHostIntegrationId,\n} from '../constants.js';\nimport {\n\tAuthenticationError,\n\tAuthenticationErrorReason,\n\tisRateLimitResponse,\n\tRequestClientError,\n\tRequestNotFoundError,\n\ttoError,\n\ttoRateLimitError,\n} from '../errors.js';\n\nconst maxProviderErrorBodyLength = 500;\n\n/**\n * A 2xx response carrying an HTML page instead of data.\n *\n * Azure DevOps answers a rejected credential by redirecting to its sign-in page, which then returns\n * `203 text/html` rather than `401`. `response.ok` spans the whole 200-299 range, so that page used to be read as\n * a successful body and handed to provider code typed as the JSON it is not — surfacing as an opaque `TypeError`\n * deep inside a provider, or as nothing at all on a route that returns `void`, which made a bad credential look\n * like a successful write (GKDEV-3617).\n *\n * `provider-apis` 0.59.0 raises its own error for this, but only from the transport it wraps; this package passes\n * its own already-parsed request function, so the check has to live here too.\n *\n * The message carries no URL, and the attached response is stripped of its credential-bearing headers and cut to\n * a diagnostic prefix: an error is something a consumer may reasonably log or forward, and the response it is\n * built from is a sign-in page carrying a session cookie.\n */\nexport class UnexpectedHtmlResponseError extends Error {\n\tstatic is(ex: unknown): ex is UnexpectedHtmlResponseError {\n\t\treturn ex instanceof UnexpectedHtmlResponseError;\n\t}\n\n\treadonly response: SanitizedErrorResponse;\n\n\tconstructor(status: number, contentType: string, response: ProviderErrorResponse | undefined) {\n\t\tsuper(`(${status}) Expected data but the provider returned an HTML page (content-type: ${contentType})`);\n\n\t\tthis.response = sanitizeErrorResponse(status, response);\n\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\ntype ProviderErrorResponse = { body?: unknown; headers?: Record<string, string>; status?: number };\ntype SanitizedErrorResponse = { body?: string; headers: Record<string, string>; status: number };\n\n/**\n * Response headers a rejected-credential page carries that must not ride along on an error: the session cookie it\n * sets, the challenges whose base64 payload decodes to the tenant or the internal domain, and the request-side\n * names a substituted fetch can echo back onto a response.\n */\nconst unloggableResponseHeaders = new Set([\n\t'authentication-info',\n\t'authorization',\n\t'cookie',\n\t'proxy-authenticate',\n\t'proxy-authentication-info',\n\t'proxy-authorization',\n\t'set-cookie',\n\t'www-authenticate',\n\t'x-api-key',\n\t'x-auth-token',\n]);\n\n/** Strips the credential-bearing headers from a response and cuts its body to a diagnostic prefix. */\nfunction sanitizeErrorResponse(status: number, response: ProviderErrorResponse | undefined): SanitizedErrorResponse {\n\tconst headers: Record<string, string> = {};\n\tfor (const [name, value] of Object.entries(response?.headers ?? {})) {\n\t\tif (unloggableResponseHeaders.has(name.toLowerCase())) continue;\n\n\t\theaders[name] = value;\n\t}\n\n\tconst body = response?.body;\n\treturn {\n\t\t// A sign-in page runs to tens of kilobytes and none of it is a diagnostic; keep only what identifies it.\n\t\t...(typeof body === 'string' ? { body: body.slice(0, maxProviderErrorBodyLength) } : {}),\n\t\theaders: headers,\n\t\tstatus: response?.status ?? status,\n\t};\n}\n\nconst linearIssueNotFoundMessage = /^Linear issue not found: .+$/i;\n\ntype ProviderGraphQLError = {\n\tmessage?: unknown;\n\textensions?: { code?: unknown };\n};\n\nfunction getProviderGraphQLErrors(body: unknown): ProviderGraphQLError[] {\n\tif (body == null || typeof body !== 'object') return [];\n\n\tconst errors = (body as { errors?: unknown }).errors;\n\treturn Array.isArray(errors)\n\t\t? errors.filter((error): error is ProviderGraphQLError => error != null && typeof error === 'object')\n\t\t: [];\n}\n\nexport function isAzureProviderId(\n\tproviderId: IntegrationIds,\n): providerId is GitCloudHostIntegrationId.AzureDevOps | GitSelfManagedHostIntegrationId.AzureDevOpsServer {\n\treturn (\n\t\tproviderId === GitCloudHostIntegrationId.AzureDevOps ||\n\t\tproviderId === GitSelfManagedHostIntegrationId.AzureDevOpsServer\n\t);\n}\n\nexport function isGitHubProviderId(\n\tproviderId: IntegrationIds,\n): providerId is GitCloudHostIntegrationId.GitHub | GitSelfManagedHostIntegrationId.CloudGitHubEnterprise {\n\treturn (\n\t\tproviderId === GitCloudHostIntegrationId.GitHub ||\n\t\tproviderId === GitSelfManagedHostIntegrationId.CloudGitHubEnterprise\n\t);\n}\n\n/**\n * GitHub's GraphQL schema declares coordinate variables (issue/PR number) as `Int!`, a 32-bit signed integer.\n * A number above this coerces with no `path` on the error, which fails every aliased target in the chunk rather\n * than just the one with the bad number — so the read layer refuses the whole call up front instead.\n */\nexport const githubGraphQLInt32Max = 2147483647;\n\nfunction isLinearRateLimitError(providerId: IntegrationIds, ex: unknown): boolean {\n\tif (providerId !== IssuesCloudHostIntegrationId.Linear) return false;\n\n\tconst body = (ex as { response?: { body?: unknown } }).response?.body;\n\treturn getProviderGraphQLErrors(body).some(error => error.extensions?.code === 'RATELIMITED');\n}\n\nexport function isProviderIssueNotFoundError(providerId: IntegrationIds, ex: unknown): boolean {\n\tif (providerId === IssuesCloudHostIntegrationId.Linear) {\n\t\treturn ex instanceof Error && linearIssueNotFoundMessage.test(ex.message);\n\t}\n\n\tconst status = (ex as { response?: { status?: unknown } }).response?.status;\n\treturn status === 404 || status === 410 || status === 422;\n}\n\nconst jiraMissingProjectMessage = /^The value '.*' does not exist for the field 'project'\\.?$/i;\n\n/**\n * Jira's refusal of a search scoped to a project it cannot find: `400` with\n * `{\"errorMessages\":[\"The value 'KPSC290' does not exist for the field 'project'.\"]}`, measured against Jira Data\n * Center 10.7.3 for a deleted project key and for a numeric id that names no project. Jira answers a project the\n * user lost browse permission on the same way, since it will not confirm a project the user cannot see.\n *\n * Only a `400` whose EVERY message is that one: a JQL that is wrong in some other way also answers `400`, and that\n * is a real failure rather than a project that is simply gone. Accepts the SDK's error or the `RequestClientError`\n * `throwProviderError` wraps it in, whose `original` keeps the response.\n */\nexport function isJiraMissingProjectError(ex: unknown): boolean {\n\tfor (const candidate of [ex, (ex as { original?: unknown } | undefined)?.original]) {\n\t\tconst response = (candidate as { response?: { status?: unknown; body?: unknown } } | undefined)?.response;\n\t\tif (response?.status !== 400) continue;\n\n\t\tconst messages = (response.body as { errorMessages?: unknown } | null | undefined)?.errorMessages;\n\t\tif (!Array.isArray(messages) || messages.length === 0) continue;\n\n\t\treturn messages.every(m => typeof m === 'string' && jiraMissingProjectMessage.test(m.trim()));\n\t}\n\n\treturn false;\n}\n\nexport function throwProviderError(tokenWithInfo: TokenWithInfo, error: unknown): never {\n\tconst { accessToken: token, ...tokenInfo } = tokenWithInfo;\n\tconst providerId = tokenWithInfo.providerId;\n\tconst normalizedError = toError(error);\n\t// A page served with a 2xx is only known to mean a rejected credential on Azure DevOps, which answers one by\n\t// redirecting to its sign-in page (GKDEV-3617). Elsewhere it is far more likely a maintenance or WAF page in\n\t// front of a provider, and calling that an authentication failure is not free: `handleProviderException`\n\t// expires a valid cloud session on one, and counts it against the budget that disconnects the integration.\n\t// So Azure gets the specific diagnosis and every other provider gets the honest one — the response was not\n\t// data, which is a protocol failure.\n\tif (UnexpectedHtmlResponseError.is(error)) {\n\t\tthrow isAzureProviderId(providerId)\n\t\t\t? new AuthenticationError(tokenInfo, AuthenticationErrorReason.Unauthorized, normalizedError)\n\t\t\t: new RequestClientError(normalizedError);\n\t}\n\n\t// Linear reports GraphQL throttling as HTTP 400, so its structured code must win over generic 4xx handling.\n\tif (isLinearRateLimitError(providerId, error)) {\n\t\tthrow toRateLimitError(normalizedError, token);\n\t}\n\n\tconst status = (error as { response?: { status?: unknown } }).response?.status;\n\tif (typeof status === 'number') {\n\t\tswitch (status) {\n\t\t\tcase 404:\n\t\t\tcase 410:\n\t\t\tcase 422:\n\t\t\t\tthrow new RequestNotFoundError(normalizedError);\n\t\t\tcase 429:\n\t\t\t\tthrow toRateLimitError(normalizedError, token);\n\t\t\tcase 401:\n\t\t\tcase 403:\n\t\t\t\t// Some hosts overload 403 for throttling; classify that before asking the user to reconnect.\n\t\t\t\tif (isRateLimitResponse({ status: status, message: normalizedError.message })) {\n\t\t\t\t\tthrow toRateLimitError(normalizedError, token);\n\t\t\t\t}\n\t\t\t\tthrow new AuthenticationError(\n\t\t\t\t\ttokenInfo,\n\t\t\t\t\tstatus === 401 ? AuthenticationErrorReason.Unauthorized : AuthenticationErrorReason.Forbidden,\n\t\t\t\t\tnormalizedError,\n\t\t\t\t);\n\t\t\tdefault:\n\t\t\t\tif (status >= 400 && status < 500) {\n\t\t\t\t\tthrow new RequestClientError(normalizedError);\n\t\t\t\t}\n\t\t}\n\t}\n\n\tthrow error;\n}\n\n/** Extracts the useful provider prose from an already-parsed SDK response body. */\nexport function getProviderResponseBodyMessage(body: unknown): string | undefined {\n\tlet message: string | undefined;\n\tif (typeof body === 'string') {\n\t\tmessage = body;\n\t} else if (body != null && typeof body === 'object') {\n\t\tconst { message: direct, error } = body as { message?: unknown; error?: unknown };\n\t\tif (typeof direct === 'string') {\n\t\t\tmessage = direct;\n\t\t} else if (typeof error === 'string') {\n\t\t\tmessage = error;\n\t\t} else if (error != null && typeof error === 'object') {\n\t\t\tconst nested = (error as { message?: unknown }).message;\n\t\t\tif (typeof nested === 'string') {\n\t\t\t\tmessage = nested;\n\t\t\t}\n\t\t}\n\t\tif (message == null) {\n\t\t\tconst graphQLError = getProviderGraphQLErrors(body)[0];\n\t\t\tif (typeof graphQLError?.message === 'string') {\n\t\t\t\tmessage = graphQLError.message;\n\t\t\t} else if (typeof graphQLError?.extensions?.code === 'string') {\n\t\t\t\tmessage = graphQLError.extensions.code;\n\t\t\t}\n\t\t}\n\t}\n\n\tmessage = message?.trim();\n\treturn message ? message.slice(0, maxProviderErrorBodyLength) : undefined;\n}\n"],"mappings":";;;;AAiBA,MAAM,6BAA6B;;;;;;;;;;;;;;;;;AAkBnC,IAAa,8BAAb,MAAa,oCAAoC,MAAM;CACtD,OAAO,GAAG,IAAgD;EACzD,OAAO,cAAc;CACtB;CAEA;CAEA,YAAY,QAAgB,aAAqB,UAA6C;EAC7F,MAAM,IAAI,OAAO,wEAAwE,YAAY,EAAE;EAEvG,KAAK,WAAW,sBAAsB,QAAQ,QAAQ;EAEtD,MAAM,oBAAoB,MAAM,UAAU;CAC3C;AACD;;;;;;AAUA,MAAM,4CAA4B,IAAI,IAAI;CACzC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD,CAAC;;AAGD,SAAS,sBAAsB,QAAgB,UAAqE;CACnH,MAAM,UAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,UAAU,WAAW,CAAC,CAAC,GAAG;EACpE,IAAI,0BAA0B,IAAI,KAAK,YAAY,CAAC,GAAG;EAEvD,QAAQ,QAAQ;CACjB;CAEA,MAAM,OAAO,UAAU;CACvB,OAAO;EAEN,GAAI,OAAO,SAAS,WAAW,EAAE,MAAM,KAAK,MAAM,GAAG,0BAA0B,EAAE,IAAI,CAAC;EAC7E;EACT,QAAQ,UAAU,UAAU;CAC7B;AACD;AAEA,MAAM,6BAA6B;AAOnC,SAAS,yBAAyB,MAAuC;CACxE,IAAI,QAAQ,QAAQ,OAAO,SAAS,UAAU,OAAO,CAAC;CAEtD,MAAM,SAAU,KAA8B;CAC9C,OAAO,MAAM,QAAQ,MAAM,IACxB,OAAO,QAAQ,UAAyC,SAAS,QAAQ,OAAO,UAAU,QAAQ,IAClG,CAAC;AACL;AAEA,SAAgB,kBACf,YAC0G;CAC1G,OACC,eAAA,iBACA,eAAA;AAEF;AAEA,SAAgB,mBACf,YACyG;CACzG,OACC,eAAA,YACA,eAAA;AAEF;;;;;;AAOA,MAAa,wBAAwB;AAErC,SAAS,uBAAuB,YAA4B,IAAsB;CACjF,IAAI,eAAA,UAAoD,OAAO;CAE/D,MAAM,OAAQ,GAAyC,UAAU;CACjE,OAAO,yBAAyB,IAAI,CAAC,CAAC,MAAK,UAAS,MAAM,YAAY,SAAS,aAAa;AAC7F;AAEA,SAAgB,6BAA6B,YAA4B,IAAsB;CAC9F,IAAI,eAAA,UACH,OAAO,cAAc,SAAS,2BAA2B,KAAK,GAAG,OAAO;CAGzE,MAAM,SAAU,GAA2C,UAAU;CACrE,OAAO,WAAW,OAAO,WAAW,OAAO,WAAW;AACvD;AAEA,MAAM,4BAA4B;;;;;;;;;;;AAYlC,SAAgB,0BAA0B,IAAsB;CAC/D,KAAK,MAAM,aAAa,CAAC,IAAK,IAA2C,QAAQ,GAAG;EACnF,MAAM,WAAY,WAA+E;EACjG,IAAI,UAAU,WAAW,KAAK;EAE9B,MAAM,WAAY,SAAS,MAAyD;EACpF,IAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,GAAG;EAEvD,OAAO,SAAS,OAAM,MAAK,OAAO,MAAM,YAAY,0BAA0B,KAAK,EAAE,KAAK,CAAC,CAAC;CAC7F;CAEA,OAAO;AACR;AAEA,SAAgB,mBAAmB,eAA8B,OAAuB;CACvF,MAAM,EAAE,aAAa,OAAO,GAAG,cAAc;CAC7C,MAAM,aAAa,cAAc;CACjC,MAAM,kBAAkB,QAAQ,KAAK;CAOrC,IAAI,4BAA4B,GAAG,KAAK,GACvC,MAAM,kBAAkB,UAAU,IAC/B,IAAI,oBAAoB,WAAA,GAAmD,eAAe,IAC1F,IAAI,mBAAmB,eAAe;CAI1C,IAAI,uBAAuB,YAAY,KAAK,GAC3C,MAAM,iBAAiB,iBAAiB,KAAK;CAG9C,MAAM,SAAU,MAA8C,UAAU;CACxE,IAAI,OAAO,WAAW,UACrB,QAAQ,QAAR;EACC,KAAK;EACL,KAAK;EACL,KAAK,KACJ,MAAM,IAAI,qBAAqB,eAAe;EAC/C,KAAK,KACJ,MAAM,iBAAiB,iBAAiB,KAAK;EAC9C,KAAK;EACL,KAAK;GAEJ,IAAI,oBAAoB;IAAU;IAAQ,SAAS,gBAAgB;GAAQ,CAAC,GAC3E,MAAM,iBAAiB,iBAAiB,KAAK;GAE9C,MAAM,IAAI,oBACT,WACA,WAAW,MAAA,IAAA,GACX,eACD;EACD,SACC,IAAI,UAAU,OAAO,SAAS,KAC7B,MAAM,IAAI,mBAAmB,eAAe;CAE/C;CAGD,MAAM;AACP;;AAGA,SAAgB,+BAA+B,MAAmC;CACjF,IAAI;CACJ,IAAI,OAAO,SAAS,UACnB,UAAU;MACJ,IAAI,QAAQ,QAAQ,OAAO,SAAS,UAAU;EACpD,MAAM,EAAE,SAAS,QAAQ,UAAU;EACnC,IAAI,OAAO,WAAW,UACrB,UAAU;OACJ,IAAI,OAAO,UAAU,UAC3B,UAAU;OACJ,IAAI,SAAS,QAAQ,OAAO,UAAU,UAAU;GACtD,MAAM,SAAU,MAAgC;GAChD,IAAI,OAAO,WAAW,UACrB,UAAU;EAEZ;EACA,IAAI,WAAW,MAAM;GACpB,MAAM,eAAe,yBAAyB,IAAI,CAAC,CAAC;GACpD,IAAI,OAAO,cAAc,YAAY,UACpC,UAAU,aAAa;QACjB,IAAI,OAAO,cAAc,YAAY,SAAS,UACpD,UAAU,aAAa,WAAW;EAEpC;CACD;CAEA,UAAU,SAAS,KAAK;CACxB,OAAO,UAAU,QAAQ,MAAM,GAAG,0BAA0B,IAAI,KAAA;AACjE"}
|
|
1
|
+
{"version":3,"file":"providerErrors.js","names":[],"sources":["../../../../../plus/integrations/src/providers/providerErrors.ts"],"sourcesContent":["import type { TokenWithInfo } from '../authentication/models.js';\nimport type { IntegrationIds } from '../constants.js';\nimport {\n\tGitCloudHostIntegrationId,\n\tGitSelfManagedHostIntegrationId,\n\tIssuesCloudHostIntegrationId,\n} from '../constants.js';\nimport {\n\tAuthenticationError,\n\tAuthenticationErrorReason,\n\tisRateLimitResponse,\n\tRequestClientError,\n\tRequestNotFoundError,\n\ttoError,\n\ttoRateLimitError,\n} from '../errors.js';\n\nconst maxProviderErrorBodyLength = 500;\n\n/**\n * A 2xx response carrying an HTML page instead of data.\n *\n * Azure DevOps answers a rejected credential by redirecting to its sign-in page, which then returns\n * `203 text/html` rather than `401`. `response.ok` spans the whole 200-299 range, so that page used to be read as\n * a successful body and handed to provider code typed as the JSON it is not — surfacing as an opaque `TypeError`\n * deep inside a provider, or as nothing at all on a route that returns `void`, which made a bad credential look\n * like a successful write (GKDEV-3617).\n *\n * `provider-apis` 0.59.0 raises its own error for this, but only from the transport it wraps; this package passes\n * its own already-parsed request function, so the check has to live here too.\n *\n * The message carries no URL, and the attached response is stripped of its credential-bearing headers and cut to\n * a diagnostic prefix: an error is something a consumer may reasonably log or forward, and the response it is\n * built from is a sign-in page carrying a session cookie.\n */\nexport class UnexpectedHtmlResponseError extends Error {\n\tstatic is(ex: unknown): ex is UnexpectedHtmlResponseError {\n\t\treturn ex instanceof UnexpectedHtmlResponseError;\n\t}\n\n\treadonly response: SanitizedErrorResponse;\n\n\tconstructor(status: number, contentType: string, response: ProviderErrorResponse | undefined) {\n\t\tsuper(`(${status}) Expected data but the provider returned an HTML page (content-type: ${contentType})`);\n\n\t\tthis.response = sanitizeErrorResponse(status, response);\n\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\ntype ProviderErrorResponse = { body?: unknown; headers?: Record<string, string>; status?: number };\ntype SanitizedErrorResponse = { body?: string; headers: Record<string, string>; status: number };\n\n/**\n * Response headers a rejected-credential page carries that must not ride along on an error: the session cookie it\n * sets, the challenges whose base64 payload decodes to the tenant or the internal domain, and the request-side\n * names a substituted fetch can echo back onto a response.\n */\nconst unloggableResponseHeaders = new Set([\n\t'authentication-info',\n\t'authorization',\n\t'cookie',\n\t'proxy-authenticate',\n\t'proxy-authentication-info',\n\t'proxy-authorization',\n\t'set-cookie',\n\t'www-authenticate',\n\t'x-api-key',\n\t'x-auth-token',\n]);\n\n/** Strips the credential-bearing headers from a response and cuts its body to a diagnostic prefix. */\nfunction sanitizeErrorResponse(status: number, response: ProviderErrorResponse | undefined): SanitizedErrorResponse {\n\tconst headers: Record<string, string> = {};\n\tfor (const [name, value] of Object.entries(response?.headers ?? {})) {\n\t\tif (unloggableResponseHeaders.has(name.toLowerCase())) continue;\n\n\t\theaders[name] = value;\n\t}\n\n\tconst body = response?.body;\n\treturn {\n\t\t// A sign-in page runs to tens of kilobytes and none of it is a diagnostic; keep only what identifies it.\n\t\t...(typeof body === 'string' ? { body: body.slice(0, maxProviderErrorBodyLength) } : {}),\n\t\theaders: headers,\n\t\tstatus: response?.status ?? status,\n\t};\n}\n\nconst linearIssueNotFoundMessage = /^Linear issue not found: .+$/i;\n\n/**\n * What Linear actually answers for an identifier it can't resolve: HTTP 200 with the single GraphQL error\n * `Entity not found: Issue` (`INPUT_ERROR`), which provider-apis rethrows as `Linear GraphQL errors: <messages>`, the\n * response not attached. Only that one message proves the miss; any other error, or another alongside it, is a\n * failure.\n */\nconst linearIssueEntityNotFoundMessage = /^Linear GraphQL errors: Entity not found: Issue$/;\n\ntype ProviderGraphQLError = {\n\tmessage?: unknown;\n\textensions?: { code?: unknown };\n};\n\nfunction getProviderGraphQLErrors(body: unknown): ProviderGraphQLError[] {\n\tif (body == null || typeof body !== 'object') return [];\n\n\tconst errors = (body as { errors?: unknown }).errors;\n\treturn Array.isArray(errors)\n\t\t? errors.filter((error): error is ProviderGraphQLError => error != null && typeof error === 'object')\n\t\t: [];\n}\n\nexport function isAzureProviderId(\n\tproviderId: IntegrationIds,\n): providerId is GitCloudHostIntegrationId.AzureDevOps | GitSelfManagedHostIntegrationId.AzureDevOpsServer {\n\treturn (\n\t\tproviderId === GitCloudHostIntegrationId.AzureDevOps ||\n\t\tproviderId === GitSelfManagedHostIntegrationId.AzureDevOpsServer\n\t);\n}\n\nexport function isGitHubProviderId(\n\tproviderId: IntegrationIds,\n): providerId is GitCloudHostIntegrationId.GitHub | GitSelfManagedHostIntegrationId.CloudGitHubEnterprise {\n\treturn (\n\t\tproviderId === GitCloudHostIntegrationId.GitHub ||\n\t\tproviderId === GitSelfManagedHostIntegrationId.CloudGitHubEnterprise\n\t);\n}\n\n/**\n * GitHub's GraphQL schema declares coordinate variables (issue/PR number) as `Int!`, a 32-bit signed integer.\n * A number above this coerces with no `path` on the error, which fails every aliased target in the chunk rather\n * than just the one with the bad number — so the read layer refuses the whole call up front instead.\n */\nexport const githubGraphQLInt32Max = 2147483647;\n\nfunction isLinearRateLimitError(providerId: IntegrationIds, ex: unknown): boolean {\n\tif (providerId !== IssuesCloudHostIntegrationId.Linear) return false;\n\n\tconst body = (ex as { response?: { body?: unknown } }).response?.body;\n\treturn getProviderGraphQLErrors(body).some(error => error.extensions?.code === 'RATELIMITED');\n}\n\nexport function isProviderIssueNotFoundError(providerId: IntegrationIds, ex: unknown): boolean {\n\tif (providerId === IssuesCloudHostIntegrationId.Linear) {\n\t\treturn (\n\t\t\tex instanceof Error &&\n\t\t\t(linearIssueNotFoundMessage.test(ex.message) || linearIssueEntityNotFoundMessage.test(ex.message))\n\t\t);\n\t}\n\n\tconst status = (ex as { response?: { status?: unknown } }).response?.status;\n\treturn status === 404 || status === 410 || status === 422;\n}\n\nconst jiraMissingProjectMessage = /^The value '.*' does not exist for the field 'project'\\.?$/i;\n\n/**\n * Jira's refusal of a search scoped to a project it cannot find: `400` with\n * `{\"errorMessages\":[\"The value 'KPSC290' does not exist for the field 'project'.\"]}`, measured against Jira Data\n * Center 10.7.3 for a deleted project key and for a numeric id that names no project. Jira answers a project the\n * user lost browse permission on the same way, since it will not confirm a project the user cannot see.\n *\n * Only a `400` whose EVERY message is that one: a JQL that is wrong in some other way also answers `400`, and that\n * is a real failure rather than a project that is simply gone. Accepts the SDK's error or the `RequestClientError`\n * `throwProviderError` wraps it in, whose `original` keeps the response.\n */\nexport function isJiraMissingProjectError(ex: unknown): boolean {\n\tfor (const candidate of [ex, (ex as { original?: unknown } | undefined)?.original]) {\n\t\tconst response = (candidate as { response?: { status?: unknown; body?: unknown } } | undefined)?.response;\n\t\tif (response?.status !== 400) continue;\n\n\t\tconst messages = (response.body as { errorMessages?: unknown } | null | undefined)?.errorMessages;\n\t\tif (!Array.isArray(messages) || messages.length === 0) continue;\n\n\t\treturn messages.every(m => typeof m === 'string' && jiraMissingProjectMessage.test(m.trim()));\n\t}\n\n\treturn false;\n}\n\nexport function throwProviderError(tokenWithInfo: TokenWithInfo, error: unknown): never {\n\tconst { accessToken: token, ...tokenInfo } = tokenWithInfo;\n\tconst providerId = tokenWithInfo.providerId;\n\tconst normalizedError = toError(error);\n\t// A page served with a 2xx is only known to mean a rejected credential on Azure DevOps, which answers one by\n\t// redirecting to its sign-in page (GKDEV-3617). Elsewhere it is far more likely a maintenance or WAF page in\n\t// front of a provider, and calling that an authentication failure is not free: `handleProviderException`\n\t// expires a valid cloud session on one, and counts it against the budget that disconnects the integration.\n\t// So Azure gets the specific diagnosis and every other provider gets the honest one — the response was not\n\t// data, which is a protocol failure.\n\tif (UnexpectedHtmlResponseError.is(error)) {\n\t\tthrow isAzureProviderId(providerId)\n\t\t\t? new AuthenticationError(tokenInfo, AuthenticationErrorReason.Unauthorized, normalizedError)\n\t\t\t: new RequestClientError(normalizedError);\n\t}\n\n\t// Linear reports GraphQL throttling as HTTP 400, so its structured code must win over generic 4xx handling.\n\tif (isLinearRateLimitError(providerId, error)) {\n\t\tthrow toRateLimitError(normalizedError, token);\n\t}\n\n\tconst status = (error as { response?: { status?: unknown } }).response?.status;\n\tif (typeof status === 'number') {\n\t\tswitch (status) {\n\t\t\tcase 404:\n\t\t\tcase 410:\n\t\t\tcase 422:\n\t\t\t\tthrow new RequestNotFoundError(normalizedError);\n\t\t\tcase 429:\n\t\t\t\tthrow toRateLimitError(normalizedError, token);\n\t\t\tcase 401:\n\t\t\tcase 403:\n\t\t\t\t// Some hosts overload 403 for throttling; classify that before asking the user to reconnect.\n\t\t\t\tif (isRateLimitResponse({ status: status, message: normalizedError.message })) {\n\t\t\t\t\tthrow toRateLimitError(normalizedError, token);\n\t\t\t\t}\n\t\t\t\tthrow new AuthenticationError(\n\t\t\t\t\ttokenInfo,\n\t\t\t\t\tstatus === 401 ? AuthenticationErrorReason.Unauthorized : AuthenticationErrorReason.Forbidden,\n\t\t\t\t\tnormalizedError,\n\t\t\t\t);\n\t\t\tdefault:\n\t\t\t\tif (status >= 400 && status < 500) {\n\t\t\t\t\tthrow new RequestClientError(normalizedError);\n\t\t\t\t}\n\t\t}\n\t}\n\n\tthrow error;\n}\n\n/** Extracts the useful provider prose from an already-parsed SDK response body. */\nexport function getProviderResponseBodyMessage(body: unknown): string | undefined {\n\tlet message: string | undefined;\n\tif (typeof body === 'string') {\n\t\tmessage = body;\n\t} else if (body != null && typeof body === 'object') {\n\t\tconst { message: direct, error } = body as { message?: unknown; error?: unknown };\n\t\tif (typeof direct === 'string') {\n\t\t\tmessage = direct;\n\t\t} else if (typeof error === 'string') {\n\t\t\tmessage = error;\n\t\t} else if (error != null && typeof error === 'object') {\n\t\t\tconst nested = (error as { message?: unknown }).message;\n\t\t\tif (typeof nested === 'string') {\n\t\t\t\tmessage = nested;\n\t\t\t}\n\t\t}\n\t\tif (message == null) {\n\t\t\tconst graphQLError = getProviderGraphQLErrors(body)[0];\n\t\t\tif (typeof graphQLError?.message === 'string') {\n\t\t\t\tmessage = graphQLError.message;\n\t\t\t} else if (typeof graphQLError?.extensions?.code === 'string') {\n\t\t\t\tmessage = graphQLError.extensions.code;\n\t\t\t}\n\t\t}\n\t}\n\n\tmessage = message?.trim();\n\treturn message ? message.slice(0, maxProviderErrorBodyLength) : undefined;\n}\n"],"mappings":"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"}
|
|
@@ -3,12 +3,14 @@ import { IssueSorting } from "../../../git/models/issue.js";
|
|
|
3
3
|
import { PullRequest, PullRequestFilter, PullRequestMergeMethod } from "../../../git/models/pullRequest.js";
|
|
4
4
|
import { TokenOptInfo, TokenWithInfo } from "../authentication/models.js";
|
|
5
5
|
import { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId, IntegrationIds, IssuesCloudHostIntegrationId, IssuesSelfManagedHostIntegrationId } from "../constants.js";
|
|
6
|
-
import { IssueFilter } from "../providerFilters.js";
|
|
7
6
|
import { ProviderOrganization } from "../results.js";
|
|
8
7
|
import { ProviderPullRequestCount, ProviderPullRequestSearchPage } from "../models/pullRequestReads.js";
|
|
9
|
-
import {
|
|
10
|
-
import { countBitbucketServerPullRequests, searchBitbucketServerPullRequestsPage } from "./bitbucket-server/pullRequestSearch.js";
|
|
8
|
+
import { IssueFilter } from "../providerFilters.js";
|
|
11
9
|
import { GetIssuesOptions, GetPullRequestsForUserOptions, GetPullRequestsOptions, GetReposOptions, PageInfo, PagingInput, PagingMode, ProviderAccount, ProviderApiCollectionResult, ProviderApiPagedResult, ProviderAzureProject, ProviderAzureResource, ProviderBitbucketResource, ProviderGitHubOrganization, ProviderGitLabGroup, ProviderHierarchyResult, ProviderIssue, ProviderJiraProject, ProviderJiraResource, ProviderJiraServerProject, ProviderLinearOrganization, ProviderLinearTeam, ProviderPullRequest, ProviderRepoInput, ProviderReposInput, ProviderRepository, ProviderRequestResponse } from "./models.js";
|
|
10
|
+
import { AzurePullRequest, AzureWorkItemResponse } from "./azure/models.js";
|
|
11
|
+
import { countBitbucketServerPullRequests, searchBitbucketServerPullRequestsPage } from "./bitbucket-server/pullRequestSearch.js";
|
|
12
|
+
import { JiraIssueEtagResponse } from "./jiraIssueByKey.js";
|
|
13
|
+
import { LinearIssueEtagNode } from "./linearIssuesEtag.js";
|
|
12
14
|
import { IntegrationAuthenticationService } from "../authentication/integrationAuthenticationService.js";
|
|
13
15
|
import { CollectionMetadata, GitIssueState, GitPullRequestState, TrelloBoard, TrelloList } from "@gitkraken/provider-apis";
|
|
14
16
|
//#region ../plus/integrations/src/providers/providersApi.d.ts
|
|
@@ -70,6 +72,21 @@ export declare class ProvidersApi {
|
|
|
70
72
|
isPAT?: boolean;
|
|
71
73
|
baseUrl?: string;
|
|
72
74
|
}): Promise<AzurePullRequest[] | undefined>;
|
|
75
|
+
/**
|
|
76
|
+
* The ids of every group `userId` is a member of in one Azure DevOps organization (Azure DevOps Server: one
|
|
77
|
+
* collection), directly or through other groups: its teams and security groups, in every project and at the
|
|
78
|
+
* organization level. A group is an identity like a person, so these are the ids a pull request lists a group under
|
|
79
|
+
* as a reviewer. provider-apis has no identity read, so this asks Azure directly, with the credential provider-apis
|
|
80
|
+
* would send.
|
|
81
|
+
*
|
|
82
|
+
* Two requests, however many groups: the user's expanded membership, which names its groups only by descriptor,
|
|
83
|
+
* then one batch read resolving those descriptors to ids. Azure DevOps Services serves identities from its
|
|
84
|
+
* `vssps` host; Azure DevOps Server from the collection itself.
|
|
85
|
+
*/
|
|
86
|
+
getAzureGroupIdsForUser(tokenOptInfo: TokenOptInfo, namespace: string, userId: string, options: {
|
|
87
|
+
isPAT?: boolean;
|
|
88
|
+
baseUrl?: string;
|
|
89
|
+
}): Promise<string[]>;
|
|
73
90
|
/**
|
|
74
91
|
* One issue by repository and number through provider-apis' GitLab `getIssue`, in the shape its list reads
|
|
75
92
|
* return. Strict, unlike {@link getIssue}: `undefined` only when provider-apis reports the project or issue
|
|
@@ -100,6 +117,35 @@ export declare class ProvidersApi {
|
|
|
100
117
|
isPAT?: boolean;
|
|
101
118
|
baseUrl?: string;
|
|
102
119
|
}): Promise<ProviderIssue | undefined>;
|
|
120
|
+
/**
|
|
121
|
+
* The cheap check behind the batch issue read's etags: `ids` (at most `azureWorkItemsEtagFieldsMaxIds`) in ONE
|
|
122
|
+
* request, where {@link getAzureWorkItem} reads one work item per request, selecting only
|
|
123
|
+
* `azureWorkItemEtagFieldNames`. Asked with `errorPolicy=omit`, without which one missing id fails the whole
|
|
124
|
+
* request; Azure then leaves out every id it can't return, for whatever reason, so a caller matches the work items
|
|
125
|
+
* to its ids by `id`, never by position, and reads an omission as unknown rather than absent. Every failure throws,
|
|
126
|
+
* classified as {@link getAzureWorkItem} classifies it.
|
|
127
|
+
*/
|
|
128
|
+
getAzureWorkItemsEtagFields(tokenOptInfo: TokenOptInfo, scope: {
|
|
129
|
+
namespace: string;
|
|
130
|
+
project: string;
|
|
131
|
+
}, ids: readonly number[], options: {
|
|
132
|
+
isPAT?: boolean;
|
|
133
|
+
baseUrl?: string;
|
|
134
|
+
}): Promise<AzureWorkItemResponse[]>;
|
|
135
|
+
/**
|
|
136
|
+
* The cheap check behind the batch pull request read's etags: one pull request as Azure DevOps returns it, by the
|
|
137
|
+
* same request {@link getPullRequestForRepo} has provider-apis send, but without the repository read it adds for
|
|
138
|
+
* clone URLs. `undefined` only by the same not-found rule as {@link getPullRequestForRepo}; every other failure
|
|
139
|
+
* throws.
|
|
140
|
+
*/
|
|
141
|
+
getAzurePullRequest(tokenOptInfo: TokenOptInfo, repo: {
|
|
142
|
+
namespace: string;
|
|
143
|
+
project: string;
|
|
144
|
+
name: string;
|
|
145
|
+
}, id: number, options: {
|
|
146
|
+
isPAT?: boolean;
|
|
147
|
+
baseUrl?: string;
|
|
148
|
+
}): Promise<AzurePullRequest | undefined>;
|
|
103
149
|
getCurrentUser(tokenOptInfo: TokenOptInfo, options?: {
|
|
104
150
|
isPAT?: boolean;
|
|
105
151
|
baseUrl?: string;
|
|
@@ -130,6 +176,11 @@ export declare class ProvidersApi {
|
|
|
130
176
|
/** See {@link GetIssuesOptions.sort}. Linear expresses `created`/`updated`, descending only. */
|
|
131
177
|
sort?: IssueSorting;
|
|
132
178
|
}): Promise<ProviderApiPagedResult<ProviderIssue>>;
|
|
179
|
+
/**
|
|
180
|
+
* The change state of up to `linearIssuesEtagMaxNumbers` issues of one Linear team, by number, in one GraphQL
|
|
181
|
+
* request. Its failures are classified as {@link getIssue}'s are, so a throttled or refused check reads the same.
|
|
182
|
+
*/
|
|
183
|
+
getLinearIssuesEtagFields(tokenOptInfo: TokenWithInfo<IssuesCloudHostIntegrationId.Linear>, teamKey: string, numbers: readonly number[]): Promise<LinearIssueEtagNode[]>;
|
|
133
184
|
/** Resolves Linear's current user (viewer). The viewer query returns only id/name/email/displayName. */
|
|
134
185
|
getLinearCurrentUser(tokenOptInfo: TokenWithInfo<IssuesCloudHostIntegrationId.Linear>): Promise<{
|
|
135
186
|
id: string;
|
|
@@ -365,6 +416,14 @@ export declare class ProvidersApi {
|
|
|
365
416
|
baseUrl?: string;
|
|
366
417
|
}): Promise<PagedResult<ProviderIssue>>;
|
|
367
418
|
getJiraIssueByKey(tokenOptInfo: TokenWithInfo<IssuesCloudHostIntegrationId.Jira>, resourceId: string, resourceUrl: string, key: string): Promise<ProviderIssue | undefined>;
|
|
419
|
+
/**
|
|
420
|
+
* The change state of up to `jiraBulkFetchMaxKeys` issues of one Jira Cloud site, in one bulk fetch. Unlike
|
|
421
|
+
* {@link getJiraIssueByKey}, a 404 is a failure: it names a wrong site or endpoint, never an absent issue.
|
|
422
|
+
*/
|
|
423
|
+
getJiraIssuesEtagFields(tokenOptInfo: TokenWithInfo<IssuesCloudHostIntegrationId.Jira>, resourceId: string, keys: readonly string[]): Promise<{
|
|
424
|
+
issues: JiraIssueEtagResponse[];
|
|
425
|
+
errorCount: number;
|
|
426
|
+
}>;
|
|
368
427
|
getIssue(tokenOptInfo: TokenWithInfo, input: {
|
|
369
428
|
resourceId: string;
|
|
370
429
|
number: string;
|
|
@@ -7,8 +7,9 @@ import { isIncompleteCollection } from "../collectionMetadata.js";
|
|
|
7
7
|
import { toTokenWithInfo } from "../authentication/models.js";
|
|
8
8
|
import { collectProviderPagedResult, mergeCollectionMetadata, parsePageCursor, toPageCursor } from "./utils/providerPaging.js";
|
|
9
9
|
import { isRepoIdsInput, providersMetadata } from "./models.js";
|
|
10
|
-
import { encodeAzurePathSegment, fromAzureWorkItemToProviderIssue } from "./azure/models.js";
|
|
11
|
-
import { requestJiraIssueByKey } from "./jiraIssueByKey.js";
|
|
10
|
+
import { azureWorkItemEtagFieldNames, encodeAzurePathSegment, fromAzureWorkItemToProviderIssue } from "./azure/models.js";
|
|
11
|
+
import { requestJiraIssueByKey, requestJiraIssuesEtagFields } from "./jiraIssueByKey.js";
|
|
12
|
+
import { requestLinearIssuesEtagFields } from "./linearIssuesEtag.js";
|
|
12
13
|
import { requestBitbucketServerProjects, requestBitbucketServerRepositories } from "./bitbucket-server/discovery.js";
|
|
13
14
|
import { countBitbucketServerPullRequests, searchBitbucketServerPullRequestsPage } from "./bitbucket-server/pullRequestSearch.js";
|
|
14
15
|
import ProviderApis from "@gitkraken/provider-apis";
|
|
@@ -39,7 +40,15 @@ function isAzureNotFoundResponse(ex, typeKeys) {
|
|
|
39
40
|
const typeKey = response.body.typeKey;
|
|
40
41
|
return typeof typeKey === "string" && typeKeys.has(typeKey);
|
|
41
42
|
}
|
|
43
|
+
/** The credential provider-apis sends Azure DevOps: a PAT as Basic (encoded here, as it does), anything else as a bearer. */
|
|
44
|
+
function azureAuthorization(token, isPAT) {
|
|
45
|
+
return isPAT ? `Basic ${base64(`:${token}`)}` : `Bearer ${token}`;
|
|
46
|
+
}
|
|
42
47
|
const azureDevOpsBaseUrl = "https://dev.azure.com";
|
|
48
|
+
/** The most group descriptors one identity batch read resolves. */
|
|
49
|
+
const azureIdentityBatchSize = 100;
|
|
50
|
+
/** Azure DevOps Services serves the identity APIs from its own host, below the organization like the main one. */
|
|
51
|
+
const azureDevOpsIdentityBaseUrl = "https://vssps.dev.azure.com";
|
|
43
52
|
const trelloBaseUrl = "https://api.trello.com";
|
|
44
53
|
/**
|
|
45
54
|
* Jira computes an issue's available transitions per issue, from the workflow and the reader's permissions, so a
|
|
@@ -495,7 +504,7 @@ var ProvidersApi = class {
|
|
|
495
504
|
try {
|
|
496
505
|
const pullRequests = (await this.request({
|
|
497
506
|
url,
|
|
498
|
-
headers: { Authorization: options.isPAT
|
|
507
|
+
headers: { Authorization: azureAuthorization(token, options.isPAT) }
|
|
499
508
|
})).body?.value;
|
|
500
509
|
if (pullRequests == null) throw new Error("Azure DevOps returned no pull requests");
|
|
501
510
|
return pullRequests;
|
|
@@ -505,6 +514,67 @@ var ProvidersApi = class {
|
|
|
505
514
|
}
|
|
506
515
|
}
|
|
507
516
|
/**
|
|
517
|
+
* The ids of every group `userId` is a member of in one Azure DevOps organization (Azure DevOps Server: one
|
|
518
|
+
* collection), directly or through other groups: its teams and security groups, in every project and at the
|
|
519
|
+
* organization level. A group is an identity like a person, so these are the ids a pull request lists a group under
|
|
520
|
+
* as a reviewer. provider-apis has no identity read, so this asks Azure directly, with the credential provider-apis
|
|
521
|
+
* would send.
|
|
522
|
+
*
|
|
523
|
+
* Two requests, however many groups: the user's expanded membership, which names its groups only by descriptor,
|
|
524
|
+
* then one batch read resolving those descriptors to ids. Azure DevOps Services serves identities from its
|
|
525
|
+
* `vssps` host; Azure DevOps Server from the collection itself.
|
|
526
|
+
*/
|
|
527
|
+
async getAzureGroupIdsForUser(tokenOptInfo, namespace, userId, options) {
|
|
528
|
+
const { tokenWithInfo } = await this.ensureProviderToken(tokenOptInfo);
|
|
529
|
+
const token = tokenWithInfo.accessToken;
|
|
530
|
+
const base = options.baseUrl != null ? `${options.baseUrl.replace(/\/$/, "")}/${encodeAzurePathSegment(namespace)}` : `${azureDevOpsIdentityBaseUrl}/${encodeAzurePathSegment(namespace)}`;
|
|
531
|
+
const headers = {
|
|
532
|
+
Authorization: azureAuthorization(token, options.isPAT),
|
|
533
|
+
"Content-Type": "application/json"
|
|
534
|
+
};
|
|
535
|
+
let memberOf;
|
|
536
|
+
try {
|
|
537
|
+
const params = new URLSearchParams({
|
|
538
|
+
identityIds: userId,
|
|
539
|
+
queryMembership: "Expanded",
|
|
540
|
+
"api-version": "5.0"
|
|
541
|
+
});
|
|
542
|
+
memberOf = (await this.request({
|
|
543
|
+
url: `${base}/_apis/identities?${params.toString()}`,
|
|
544
|
+
headers
|
|
545
|
+
})).body?.value?.[0]?.memberOf;
|
|
546
|
+
} catch (e) {
|
|
547
|
+
return this.handleProviderError(tokenWithInfo, e);
|
|
548
|
+
}
|
|
549
|
+
if (!Array.isArray(memberOf) || memberOf.some((d) => typeof d !== "string")) throw new Error("Azure DevOps returned no group membership for the current user");
|
|
550
|
+
if (memberOf.length === 0) return [];
|
|
551
|
+
const chunks = [];
|
|
552
|
+
for (let i = 0; i < memberOf.length; i += azureIdentityBatchSize) chunks.push(memberOf.slice(i, i + azureIdentityBatchSize));
|
|
553
|
+
const batches = [];
|
|
554
|
+
try {
|
|
555
|
+
for (const descriptors of chunks) {
|
|
556
|
+
const batch = await this.request({
|
|
557
|
+
url: `${base}/_apis/identitybatch?api-version=5.0-preview.1`,
|
|
558
|
+
method: "POST",
|
|
559
|
+
headers,
|
|
560
|
+
body: JSON.stringify({
|
|
561
|
+
descriptors,
|
|
562
|
+
queryMembership: "None"
|
|
563
|
+
})
|
|
564
|
+
});
|
|
565
|
+
batches.push(batch.body?.value);
|
|
566
|
+
}
|
|
567
|
+
} catch (e) {
|
|
568
|
+
return this.handleProviderError(tokenWithInfo, e);
|
|
569
|
+
}
|
|
570
|
+
if (batches.some((batch, i) => !Array.isArray(batch) || batch.length !== chunks[i].length)) throw new Error("Azure DevOps did not resolve every group of the current user");
|
|
571
|
+
return batches.flat().flatMap((g) => {
|
|
572
|
+
if (g == null) return [];
|
|
573
|
+
if (typeof g.id !== "string") throw new Error("Azure DevOps returned a group without its id");
|
|
574
|
+
return [g.id];
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
508
578
|
* One issue by repository and number through provider-apis' GitLab `getIssue`, in the shape its list reads
|
|
509
579
|
* return. Strict, unlike {@link getIssue}: `undefined` only when provider-apis reports the project or issue
|
|
510
580
|
* missing, which alone is NOT proof (see the GitLab batch hook), and never for an HTTP status — a 404 there
|
|
@@ -552,7 +622,7 @@ var ProvidersApi = class {
|
|
|
552
622
|
try {
|
|
553
623
|
workItem = (await this.request({
|
|
554
624
|
url,
|
|
555
|
-
headers: { Authorization: options.isPAT
|
|
625
|
+
headers: { Authorization: azureAuthorization(token, options.isPAT) }
|
|
556
626
|
})).body;
|
|
557
627
|
} catch (e) {
|
|
558
628
|
if (isAzureNotFoundResponse(e, azureWorkItemNotFoundTypeKeys)) return void 0;
|
|
@@ -564,6 +634,53 @@ var ProvidersApi = class {
|
|
|
564
634
|
if (typeof project === "string" && project.toLowerCase() !== scope.project.toLowerCase()) throw new Error(`Azure DevOps work item ${id} is in project '${project}', not '${scope.project}'`);
|
|
565
635
|
return issue;
|
|
566
636
|
}
|
|
637
|
+
/**
|
|
638
|
+
* The cheap check behind the batch issue read's etags: `ids` (at most `azureWorkItemsEtagFieldsMaxIds`) in ONE
|
|
639
|
+
* request, where {@link getAzureWorkItem} reads one work item per request, selecting only
|
|
640
|
+
* `azureWorkItemEtagFieldNames`. Asked with `errorPolicy=omit`, without which one missing id fails the whole
|
|
641
|
+
* request; Azure then leaves out every id it can't return, for whatever reason, so a caller matches the work items
|
|
642
|
+
* to its ids by `id`, never by position, and reads an omission as unknown rather than absent. Every failure throws,
|
|
643
|
+
* classified as {@link getAzureWorkItem} classifies it.
|
|
644
|
+
*/
|
|
645
|
+
async getAzureWorkItemsEtagFields(tokenOptInfo, scope, ids, options) {
|
|
646
|
+
const { tokenWithInfo } = await this.ensureProviderToken(tokenOptInfo);
|
|
647
|
+
const token = tokenWithInfo.accessToken;
|
|
648
|
+
const baseUrl = (options.baseUrl ?? azureDevOpsBaseUrl).replace(/\/$/, "");
|
|
649
|
+
const query = `ids=${ids.join(",")}&fields=${azureWorkItemEtagFieldNames.join(",")}&errorPolicy=omit&api-version=6.0`;
|
|
650
|
+
const url = `${baseUrl}/${encodeAzurePathSegment(scope.namespace)}/${encodeAzurePathSegment(scope.project)}/_apis/wit/workitems?${query}`;
|
|
651
|
+
try {
|
|
652
|
+
const workItems = (await this.request({
|
|
653
|
+
url,
|
|
654
|
+
headers: { Authorization: options.isPAT ? `Basic ${base64(`:${token}`)}` : `Bearer ${token}` }
|
|
655
|
+
})).body?.value;
|
|
656
|
+
if (!Array.isArray(workItems)) throw new Error("Azure DevOps returned no work items");
|
|
657
|
+
return workItems.filter((w) => w != null);
|
|
658
|
+
} catch (e) {
|
|
659
|
+
return this.handleProviderError(tokenWithInfo, e);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* The cheap check behind the batch pull request read's etags: one pull request as Azure DevOps returns it, by the
|
|
664
|
+
* same request {@link getPullRequestForRepo} has provider-apis send, but without the repository read it adds for
|
|
665
|
+
* clone URLs. `undefined` only by the same not-found rule as {@link getPullRequestForRepo}; every other failure
|
|
666
|
+
* throws.
|
|
667
|
+
*/
|
|
668
|
+
async getAzurePullRequest(tokenOptInfo, repo, id, options) {
|
|
669
|
+
const { tokenWithInfo } = await this.ensureProviderToken(tokenOptInfo);
|
|
670
|
+
const token = tokenWithInfo.accessToken;
|
|
671
|
+
const url = `${(options.baseUrl ?? azureDevOpsBaseUrl).replace(/\/$/, "")}/${encodeAzurePathSegment(repo.namespace)}/${encodeAzurePathSegment(repo.project)}/_apis/git/repositories/${encodeAzurePathSegment(repo.name)}/pullrequests/${id}?api-version=7.1`;
|
|
672
|
+
try {
|
|
673
|
+
const result = await this.request({
|
|
674
|
+
url,
|
|
675
|
+
headers: { Authorization: options.isPAT ? `Basic ${base64(`:${token}`)}` : `Bearer ${token}` }
|
|
676
|
+
});
|
|
677
|
+
if (result.body == null) throw new Error(`Azure DevOps returned no pull request ${id}`);
|
|
678
|
+
return result.body;
|
|
679
|
+
} catch (e) {
|
|
680
|
+
if (isAzureNotFoundResponse(e, azurePullRequestNotFoundTypeKeys)) return void 0;
|
|
681
|
+
return this.handleProviderError(tokenWithInfo, e);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
567
684
|
async getCurrentUser(tokenOptInfo, options) {
|
|
568
685
|
const { provider, tokenWithInfo } = await this.ensureProviderTokenAndFunction(tokenOptInfo, "getCurrentUserFn");
|
|
569
686
|
const token = tokenWithInfo.accessToken;
|
|
@@ -638,6 +755,18 @@ var ProvidersApi = class {
|
|
|
638
755
|
...options
|
|
639
756
|
}, provider.getLinearIssuesFn, tokenWithInfo, options?.cursor ?? void 0);
|
|
640
757
|
}
|
|
758
|
+
/**
|
|
759
|
+
* The change state of up to `linearIssuesEtagMaxNumbers` issues of one Linear team, by number, in one GraphQL
|
|
760
|
+
* request. Its failures are classified as {@link getIssue}'s are, so a throttled or refused check reads the same.
|
|
761
|
+
*/
|
|
762
|
+
async getLinearIssuesEtagFields(tokenOptInfo, teamKey, numbers) {
|
|
763
|
+
const { tokenWithInfo } = await this.ensureProviderToken(tokenOptInfo);
|
|
764
|
+
try {
|
|
765
|
+
return await requestLinearIssuesEtagFields(this.request, tokenWithInfo.accessToken, teamKey, numbers);
|
|
766
|
+
} catch (e) {
|
|
767
|
+
return this.handleProviderError(tokenWithInfo, e);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
641
770
|
/** Resolves Linear's current user (viewer). The viewer query returns only id/name/email/displayName. */
|
|
642
771
|
async getLinearCurrentUser(tokenOptInfo) {
|
|
643
772
|
const { provider, tokenWithInfo } = await this.ensureProviderTokenAndFunction(tokenOptInfo, "getLinearCurrentUserFn");
|
|
@@ -1166,6 +1295,18 @@ var ProvidersApi = class {
|
|
|
1166
1295
|
return this.handleProviderError(tokenWithInfo, e);
|
|
1167
1296
|
}
|
|
1168
1297
|
}
|
|
1298
|
+
/**
|
|
1299
|
+
* The change state of up to `jiraBulkFetchMaxKeys` issues of one Jira Cloud site, in one bulk fetch. Unlike
|
|
1300
|
+
* {@link getJiraIssueByKey}, a 404 is a failure: it names a wrong site or endpoint, never an absent issue.
|
|
1301
|
+
*/
|
|
1302
|
+
async getJiraIssuesEtagFields(tokenOptInfo, resourceId, keys) {
|
|
1303
|
+
const { tokenWithInfo } = await this.ensureProviderToken(tokenOptInfo);
|
|
1304
|
+
try {
|
|
1305
|
+
return await requestJiraIssuesEtagFields(this.request, tokenWithInfo.accessToken, resourceId, keys);
|
|
1306
|
+
} catch (e) {
|
|
1307
|
+
return this.handleProviderError(tokenWithInfo, e);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1169
1310
|
async getIssue(tokenOptInfo, input, options) {
|
|
1170
1311
|
const { provider, tokenWithInfo } = await this.ensureProviderTokenAndFunction(tokenOptInfo, "getIssueFn");
|
|
1171
1312
|
const token = tokenWithInfo.accessToken;
|