@gitkraken/core-gitlens 0.15.5 → 0.15.7
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 +19 -1
- package/dist/git/models/issue.d.ts +14 -1
- package/dist/git/models/issue.js +3 -1
- package/dist/git/models/issue.js.map +1 -1
- package/dist/git/utils/issue.utils.js +4 -0
- package/dist/git/utils/issue.utils.js.map +1 -1
- package/dist/plus/integrations/collectionMetadata.js +4 -2
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/fieldPresence.d.ts +1 -1
- package/dist/plus/integrations/fieldPresence.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +18 -0
- package/dist/plus/integrations/models/integration.js +13 -1
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.d.ts +7 -0
- package/dist/plus/integrations/providers/azure/azure.js +26 -4
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts +11 -3
- package/dist/plus/integrations/providers/azure/models.js +37 -23
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +14 -4
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js +31 -4
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +1 -0
- package/dist/plus/integrations/providers/models.d.ts +2 -1
- package/dist/plus/integrations/providers/models.js +9 -2
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providerErrors.d.ts +22 -0
- package/dist/plus/integrations/providers/providerErrors.js +43 -2
- package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +6 -0
- package/dist/plus/integrations/providers/providersApi.js +32 -12
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +1 -0
- package/dist/plus/integrations/reads/etag.js +8 -3
- package/dist/plus/integrations/reads/etag.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +13 -3
- package/dist/plus/integrations/results.js +12 -2
- package/dist/plus/integrations/results.js.map +1 -1
- package/docs/integrations.md +29 -6
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { Logger as _logger } from "../../../utils/logger.js";
|
|
1
2
|
import { AuthenticationError, RequestClientError, RequestNotFoundError } from "../../../git/errors.js";
|
|
2
|
-
import { getAuthenticatedUser, isRateLimitResponse, toError, toRateLimitError } from "../errors.js";
|
|
3
|
+
import { getAuthenticatedUser, isProviderUnreachableError, isRateLimitResponse, toError, toRateLimitError } from "../errors.js";
|
|
3
4
|
import "../constants.js";
|
|
4
5
|
//#region ../plus/integrations/src/providers/providerErrors.ts
|
|
5
6
|
const maxProviderErrorBodyLength = 500;
|
|
@@ -31,6 +32,41 @@ var UnexpectedHtmlResponseError = class UnexpectedHtmlResponseError extends Erro
|
|
|
31
32
|
}
|
|
32
33
|
};
|
|
33
34
|
/**
|
|
35
|
+
* A provider read that failed on the data a request returned rather than on the request: `provider-apis` reads a
|
|
36
|
+
* response body as the shape it expects without checking it, so a 2xx carrying anything else (a gateway's JSON
|
|
37
|
+
* error, `{}`, `null`, a GraphQL answer with neither data nor errors) surfaces as a `TypeError` thrown deep inside the
|
|
38
|
+
* read, such as `response.body.map is not a function` from Jira Cloud's site list or Trello's boards, or
|
|
39
|
+
* `Cannot read properties of undefined (reading 'id')` from Linear's organization. See {@link throwProviderError}.
|
|
40
|
+
*
|
|
41
|
+
* The message is the readable one a warning carries. The `TypeError` is kept as `original`, and a log line, which
|
|
42
|
+
* prints the error through `toString()`, names it too.
|
|
43
|
+
*/
|
|
44
|
+
var UnexpectedResponseError = class UnexpectedResponseError extends Error {
|
|
45
|
+
original;
|
|
46
|
+
static is(ex) {
|
|
47
|
+
return ex instanceof UnexpectedResponseError;
|
|
48
|
+
}
|
|
49
|
+
constructor(original) {
|
|
50
|
+
super("Expected data but the provider returned an unexpected response");
|
|
51
|
+
this.original = original;
|
|
52
|
+
Error.captureStackTrace?.(this, new.target);
|
|
53
|
+
}
|
|
54
|
+
toString() {
|
|
55
|
+
return `${super.toString()} (${String(this.original)})`;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
/** What the request function `ProvidersApi` hands `provider-apis` threw itself: see {@link markRequestError}. */
|
|
59
|
+
const requestErrors = /* @__PURE__ */ new WeakSet();
|
|
60
|
+
/**
|
|
61
|
+
* Records an error the request function threw itself (a transport failure, or a response it refused), so
|
|
62
|
+
* {@link throwProviderError} never reads it as data a read could not handle, whatever the host's fetch throws.
|
|
63
|
+
* Returns `ex`, to rethrow.
|
|
64
|
+
*/
|
|
65
|
+
function markRequestError(ex) {
|
|
66
|
+
if (ex != null && typeof ex === "object") requestErrors.add(ex);
|
|
67
|
+
return ex;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
34
70
|
* A response that names the anonymous user (`X-AUSERNAME: anonymous`) to a request that carried a credential.
|
|
35
71
|
*
|
|
36
72
|
* Jira Data Center serves a token it rejects as the anonymous user rather than refusing it: measured against Jira
|
|
@@ -175,6 +211,11 @@ function throwProviderError(tokenWithInfo, error) {
|
|
|
175
211
|
throw new AuthenticationError(tokenInfo, status === 401 ? 2 : 3, normalizedError);
|
|
176
212
|
default: if (status >= 400 && status < 500) throw new RequestClientError(normalizedError);
|
|
177
213
|
}
|
|
214
|
+
if (error instanceof TypeError && !requestErrors.has(error) && !isProviderUnreachableError(error)) {
|
|
215
|
+
const unexpected = new UnexpectedResponseError(error);
|
|
216
|
+
_logger.warn(`${tokenWithInfo.providerId}: ${String(unexpected)}`);
|
|
217
|
+
throw unexpected;
|
|
218
|
+
}
|
|
178
219
|
throw error;
|
|
179
220
|
}
|
|
180
221
|
/** Extracts the useful provider prose from an already-parsed SDK response body. */
|
|
@@ -199,6 +240,6 @@ function getProviderResponseBodyMessage(body) {
|
|
|
199
240
|
return message ? message.slice(0, maxProviderErrorBodyLength) : void 0;
|
|
200
241
|
}
|
|
201
242
|
//#endregion
|
|
202
|
-
export { AnonymousResponseError, UnexpectedHtmlResponseError, getProviderResponseBodyMessage, githubGraphQLInt32Max, isAzureProviderId, isGitHubProviderId, isJiraMissingProjectError, isJiraServerMissingProjectError, isProviderIssueNotFoundError, throwProviderError };
|
|
243
|
+
export { AnonymousResponseError, UnexpectedHtmlResponseError, UnexpectedResponseError, getProviderResponseBodyMessage, githubGraphQLInt32Max, isAzureProviderId, isGitHubProviderId, isJiraMissingProjectError, isJiraServerMissingProjectError, isProviderIssueNotFoundError, markRequestError, throwProviderError };
|
|
203
244
|
|
|
204
245
|
//# sourceMappingURL=providerErrors.js.map
|
|
@@ -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 type { ResponseHeaders } from '../errors.js';\nimport {\n\tAuthenticationError,\n\tAuthenticationErrorReason,\n\tgetAuthenticatedUser,\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\n/**\n * A response that names the anonymous user (`X-AUSERNAME: anonymous`) to a request that carried a credential.\n *\n * Jira Data Center serves a token it rejects as the anonymous user rather than refusing it: measured against Jira\n * Data Center 10.7.3 with a revoked token, `/rest/api/2/project` answers `200 []` and a project search answers the\n * same `400` it gives a deleted project. Read as data, that is an account with no projects, and a selected project\n * looks deleted. The server only names `anonymous` when the credential did not authenticate, so the response is the\n * credential's rejection whatever its status, except a 401, which already fails as one, and a 429 or a 5xx, which\n * prove nothing about the credential.\n *\n * Its response keeps only the status and headers, stripped like {@link UnexpectedHtmlResponseError}'s.\n */\nexport class AnonymousResponseError extends Error {\n\tstatic is(ex: unknown): ex is AnonymousResponseError {\n\t\treturn ex instanceof AnonymousResponseError;\n\t}\n\n\treadonly response: SanitizedErrorResponse;\n\n\tconstructor(status: number, response: ProviderErrorResponse | undefined) {\n\t\tsuper(`(${status}) The provider served a request sent with a credential as the anonymous user`);\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\t// A rejected credential is not a missing issue, whatever status the server served it with.\n\tif (AnonymousResponseError.is(ex)) return false;\n\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\treturn getJiraMissingProjectResponse(ex) != null;\n}\n\n/**\n * {@link isJiraMissingProjectError} for Jira Data Center, which serves a credential it rejects as the anonymous user:\n * the search then names a project the anonymous user cannot see and is refused with the very same `400`, measured\n * against Jira Data Center 10.7.3 with a revoked token. Only the user it names in `X-AUSERNAME` tells the two apart,\n * so a refusal that names no user, or `anonymous`, is a failure rather than a project that is gone.\n */\nexport function isJiraServerMissingProjectError(ex: unknown): boolean {\n\tconst user = getAuthenticatedUser(getJiraMissingProjectResponse(ex)?.headers);\n\treturn user != null && user !== 'anonymous';\n}\n\nfunction getJiraMissingProjectResponse(ex: unknown): { headers?: ResponseHeaders } | undefined {\n\tfor (const candidate of [ex, (ex as { original?: unknown } | undefined)?.original]) {\n\t\tconst response = (\n\t\t\tcandidate as { response?: { status?: unknown; body?: unknown; headers?: ResponseHeaders } } | undefined\n\t\t)?.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\t\t? response\n\t\t\t: undefined;\n\t}\n\n\treturn undefined;\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\tif (AnonymousResponseError.is(error)) {\n\t\tthrow new AuthenticationError(tokenInfo, AuthenticationErrorReason.Unauthorized, 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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"}
|
|
1
|
+
{"version":3,"file":"providerErrors.js","names":[],"sources":["../../../../../plus/integrations/src/providers/providerErrors.ts"],"sourcesContent":["import { Logger } from '@gitlens/utils/logger.js';\nimport type { TokenWithInfo } from '../authentication/models.js';\nimport type { IntegrationIds } from '../constants.js';\nimport {\n\tGitCloudHostIntegrationId,\n\tGitSelfManagedHostIntegrationId,\n\tIssuesCloudHostIntegrationId,\n} from '../constants.js';\nimport type { ResponseHeaders } from '../errors.js';\nimport {\n\tAuthenticationError,\n\tAuthenticationErrorReason,\n\tgetAuthenticatedUser,\n\tisProviderUnreachableError,\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\n/**\n * A provider read that failed on the data a request returned rather than on the request: `provider-apis` reads a\n * response body as the shape it expects without checking it, so a 2xx carrying anything else (a gateway's JSON\n * error, `{}`, `null`, a GraphQL answer with neither data nor errors) surfaces as a `TypeError` thrown deep inside the\n * read, such as `response.body.map is not a function` from Jira Cloud's site list or Trello's boards, or\n * `Cannot read properties of undefined (reading 'id')` from Linear's organization. See {@link throwProviderError}.\n *\n * The message is the readable one a warning carries. The `TypeError` is kept as `original`, and a log line, which\n * prints the error through `toString()`, names it too.\n */\nexport class UnexpectedResponseError extends Error {\n\tstatic is(ex: unknown): ex is UnexpectedResponseError {\n\t\treturn ex instanceof UnexpectedResponseError;\n\t}\n\n\tconstructor(readonly original: Error) {\n\t\tsuper('Expected data but the provider returned an unexpected response');\n\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n\n\toverride toString(): string {\n\t\treturn `${super.toString()} (${String(this.original)})`;\n\t}\n}\n\n/** What the request function `ProvidersApi` hands `provider-apis` threw itself: see {@link markRequestError}. */\nconst requestErrors = new WeakSet<object>();\n\n/**\n * Records an error the request function threw itself (a transport failure, or a response it refused), so\n * {@link throwProviderError} never reads it as data a read could not handle, whatever the host's fetch throws.\n * Returns `ex`, to rethrow.\n */\nexport function markRequestError(ex: unknown): unknown {\n\tif (ex != null && typeof ex === 'object') {\n\t\trequestErrors.add(ex);\n\t}\n\treturn ex;\n}\n\n/**\n * A response that names the anonymous user (`X-AUSERNAME: anonymous`) to a request that carried a credential.\n *\n * Jira Data Center serves a token it rejects as the anonymous user rather than refusing it: measured against Jira\n * Data Center 10.7.3 with a revoked token, `/rest/api/2/project` answers `200 []` and a project search answers the\n * same `400` it gives a deleted project. Read as data, that is an account with no projects, and a selected project\n * looks deleted. The server only names `anonymous` when the credential did not authenticate, so the response is the\n * credential's rejection whatever its status, except a 401, which already fails as one, and a 429 or a 5xx, which\n * prove nothing about the credential.\n *\n * Its response keeps only the status and headers, stripped like {@link UnexpectedHtmlResponseError}'s.\n */\nexport class AnonymousResponseError extends Error {\n\tstatic is(ex: unknown): ex is AnonymousResponseError {\n\t\treturn ex instanceof AnonymousResponseError;\n\t}\n\n\treadonly response: SanitizedErrorResponse;\n\n\tconstructor(status: number, response: ProviderErrorResponse | undefined) {\n\t\tsuper(`(${status}) The provider served a request sent with a credential as the anonymous user`);\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\t// A rejected credential is not a missing issue, whatever status the server served it with.\n\tif (AnonymousResponseError.is(ex)) return false;\n\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\treturn getJiraMissingProjectResponse(ex) != null;\n}\n\n/**\n * {@link isJiraMissingProjectError} for Jira Data Center, which serves a credential it rejects as the anonymous user:\n * the search then names a project the anonymous user cannot see and is refused with the very same `400`, measured\n * against Jira Data Center 10.7.3 with a revoked token. Only the user it names in `X-AUSERNAME` tells the two apart,\n * so a refusal that names no user, or `anonymous`, is a failure rather than a project that is gone.\n */\nexport function isJiraServerMissingProjectError(ex: unknown): boolean {\n\tconst user = getAuthenticatedUser(getJiraMissingProjectResponse(ex)?.headers);\n\treturn user != null && user !== 'anonymous';\n}\n\nfunction getJiraMissingProjectResponse(ex: unknown): { headers?: ResponseHeaders } | undefined {\n\tfor (const candidate of [ex, (ex as { original?: unknown } | undefined)?.original]) {\n\t\tconst response = (\n\t\t\tcandidate as { response?: { status?: unknown; body?: unknown; headers?: ResponseHeaders } } | undefined\n\t\t)?.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\t\t? response\n\t\t\t: undefined;\n\t}\n\n\treturn undefined;\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\tif (AnonymousResponseError.is(error)) {\n\t\tthrow new AuthenticationError(tokenInfo, AuthenticationErrorReason.Unauthorized, 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\t// A `TypeError` the request function didn't throw was thrown while reading what it returned: a body that isn't the\n\t// shape the read expects (see `UnexpectedResponseError`). A transport failure that reached here some other way stays\n\t// one. Neither a session expiry nor a strike toward disconnecting, as before: nothing says the credential is at\n\t// fault, and the provider may answer properly next time. Logged here because a warning or a fan-out's scope failure\n\t// built from it carries only the readable message, and a fan-out never reaches `handleProviderException`'s log.\n\tif (error instanceof TypeError && !requestErrors.has(error) && !isProviderUnreachableError(error)) {\n\t\tconst unexpected = new UnexpectedResponseError(error);\n\t\tLogger.warn(`${tokenWithInfo.providerId}: ${String(unexpected)}`);\n\t\tthrow unexpected;\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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"}
|
|
@@ -347,6 +347,12 @@ export declare class ProvidersApi {
|
|
|
347
347
|
}): Promise<ProviderApiPagedResult<ProviderIssue>>;
|
|
348
348
|
private azureStatesScope;
|
|
349
349
|
private readAzureStatesMetadata;
|
|
350
|
+
/**
|
|
351
|
+
* The organization's rich text fields a work item's description may come from, as provider-apis reads them for
|
|
352
|
+
* its list rows. Best-effort: `undefined` on any failure, which leaves the description to the built-in fields
|
|
353
|
+
* rather than failing the work item. Logged for every failure, a body that isn't a catalog included.
|
|
354
|
+
*/
|
|
355
|
+
private getAzureWorkItemHtmlFields;
|
|
350
356
|
private getAzureTypeStates;
|
|
351
357
|
private getAzureProjectWorkItemStates;
|
|
352
358
|
private classifyAzureIssueStates;
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
+
import { Logger as _logger } from "../../../utils/logger.js";
|
|
1
2
|
import { mapSettledBounded } from "../../../utils/promise.js";
|
|
2
3
|
import { RequestNotFoundError } from "../../../git/errors.js";
|
|
3
4
|
import { base64 } from "../../../utils/base64.js";
|
|
4
5
|
import { getAuthenticatedUser, toError } from "../errors.js";
|
|
5
6
|
import "../constants.js";
|
|
6
|
-
import { AnonymousResponseError, UnexpectedHtmlResponseError, getProviderResponseBodyMessage, isAzureProviderId, isProviderIssueNotFoundError, throwProviderError } from "./providerErrors.js";
|
|
7
|
+
import { AnonymousResponseError, UnexpectedHtmlResponseError, getProviderResponseBodyMessage, isAzureProviderId, isProviderIssueNotFoundError, markRequestError, throwProviderError } from "./providerErrors.js";
|
|
7
8
|
import { isIncompleteCollection, toCollectionScopeFailure, withValidSkippedItems } from "../collectionMetadata.js";
|
|
8
9
|
import { toTokenWithInfo } from "../authentication/models.js";
|
|
9
10
|
import { collectProviderPagedResult, mergeCollectionMetadata, parsePageCursor, toPageCursor } from "./utils/providerPaging.js";
|
|
10
11
|
import { isRepoIdsInput, providersMetadata } from "./models.js";
|
|
11
|
-
import { azureIssueStateCategory, azureWorkItemEtagFieldNames, encodeAzurePathSegment, fromAzureWorkItemToProviderIssue } from "./azure/models.js";
|
|
12
|
-
import { AzureWorkItemStatesCache, getAzureWorkItemTypeStatesUrl, getAzureWorkItemTypesUrl } from "./azure/workItemStateCategories.js";
|
|
12
|
+
import { azureIssueStateCategory, azureWorkItemEtagFieldNames, encodeAzurePathSegment, fromAzureWorkItemToProviderIssue, toAzureWorkItemDescriptionFields } from "./azure/models.js";
|
|
13
|
+
import { AzureWorkItemStatesCache, getAzureWorkItemFieldsPath, getAzureWorkItemTypeStatesUrl, getAzureWorkItemTypesUrl } from "./azure/workItemStateCategories.js";
|
|
13
14
|
import { requestJiraIssueByKey, requestJiraIssuesEtagFields } from "./jiraIssueByKey.js";
|
|
14
15
|
import { requestLinearIssuesEtagFields } from "./linearIssuesEtag.js";
|
|
15
16
|
import { requestBitbucketServerProjects, requestBitbucketServerRepositories } from "./bitbucket-server/discovery.js";
|
|
16
17
|
import { countBitbucketServerPullRequests, searchBitbucketServerPullRequestsPage } from "./bitbucket-server/pullRequestSearch.js";
|
|
17
|
-
import ProviderApis, { GitIssueState } from "@gitkraken/provider-apis";
|
|
18
|
+
import ProviderApis, { GitIssueState, needsAzureWorkItemHtmlFields } from "@gitkraken/provider-apis";
|
|
18
19
|
//#region ../plus/integrations/src/providers/providersApi.ts
|
|
19
20
|
const createProviderApis = ProviderApis.default ?? ProviderApis;
|
|
20
21
|
const repoNotFoundMessage = /^Repository .+ not found$/i;
|
|
@@ -130,13 +131,17 @@ var ProvidersApi = class {
|
|
|
130
131
|
const http = authenticationService.ctx.http;
|
|
131
132
|
const userAgent = http.userAgent;
|
|
132
133
|
const customFetch = async ({ url, ...options }) => {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
try {
|
|
135
|
+
return await parseFetchResponseForApi(await http.fetch(url, {
|
|
136
|
+
...options,
|
|
137
|
+
headers: {
|
|
138
|
+
"User-Agent": userAgent,
|
|
139
|
+
...options.headers
|
|
140
|
+
}
|
|
141
|
+
}), () => hasAuthorization(options.headers));
|
|
142
|
+
} catch (ex) {
|
|
143
|
+
throw markRequestError(ex);
|
|
144
|
+
}
|
|
140
145
|
};
|
|
141
146
|
this.request = customFetch;
|
|
142
147
|
const providerApis = createProviderApis({ request: customFetch });
|
|
@@ -652,7 +657,8 @@ var ProvidersApi = class {
|
|
|
652
657
|
if (issue == null) throw new Error(`Azure DevOps returned no readable work item ${id}`);
|
|
653
658
|
const project = workItem?.fields?.["System.TeamProject"];
|
|
654
659
|
if (typeof project === "string" && project.toLowerCase() !== scope.project.toLowerCase()) throw new Error(`Azure DevOps work item ${id} is in project '${project}', not '${scope.project}'`);
|
|
655
|
-
await this.classifyAzureIssueStates([issue], tokenWithInfo, scope.namespace, scope.project, options);
|
|
660
|
+
const [htmlFields] = await Promise.all([needsAzureWorkItemHtmlFields(workItem?.fields) ? this.getAzureWorkItemHtmlFields(tokenWithInfo, scope.namespace, options) : void 0, this.classifyAzureIssueStates([issue], tokenWithInfo, scope.namespace, scope.project, options)]);
|
|
661
|
+
if (htmlFields != null) Object.assign(issue, toAzureWorkItemDescriptionFields(workItem?.fields, htmlFields));
|
|
656
662
|
return issue;
|
|
657
663
|
}
|
|
658
664
|
/**
|
|
@@ -1183,6 +1189,20 @@ var ProvidersApi = class {
|
|
|
1183
1189
|
return this.handleProviderError(token, ex);
|
|
1184
1190
|
}
|
|
1185
1191
|
}
|
|
1192
|
+
/**
|
|
1193
|
+
* The organization's rich text fields a work item's description may come from, as provider-apis reads them for
|
|
1194
|
+
* its list rows. Best-effort: `undefined` on any failure, which leaves the description to the built-in fields
|
|
1195
|
+
* rather than failing the work item. Logged for every failure, a body that isn't a catalog included.
|
|
1196
|
+
*/
|
|
1197
|
+
async getAzureWorkItemHtmlFields(token, namespace, options) {
|
|
1198
|
+
const scope = this.azureStatesScope(token, namespace, "", options);
|
|
1199
|
+
try {
|
|
1200
|
+
return await this.azureWorkItemTypeStates.getHtmlFields(scope, () => this.readAzureStatesMetadata(token, `${scope.baseUrl.replace(/\/$/, "")}/${getAzureWorkItemFieldsPath(namespace)}`, scope.isPAT));
|
|
1201
|
+
} catch (ex) {
|
|
1202
|
+
_logger.warn(`Unable to read the Azure DevOps field catalog of '${namespace}': ${String(ex)}`);
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1186
1206
|
getAzureTypeStates(token, scope, type) {
|
|
1187
1207
|
return this.azureWorkItemTypeStates.getStates(scope, type, () => this.readAzureStatesMetadata(token, getAzureWorkItemTypeStatesUrl(scope.baseUrl, scope.namespace, scope.project, type), scope.isPAT));
|
|
1188
1208
|
}
|