@gitkraken/core-gitlens 0.16.1 → 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/CHANGELOG.md +8 -1
  2. package/dist/plus/integrations/integrationService.d.ts +1 -0
  3. package/dist/plus/integrations/integrationService.js +18 -5
  4. package/dist/plus/integrations/integrationService.js.map +1 -1
  5. package/dist/plus/integrations/providers/apiClients.d.ts +2 -0
  6. package/dist/plus/integrations/providers/apiClients.js +12 -3
  7. package/dist/plus/integrations/providers/apiClients.js.map +1 -1
  8. package/dist/plus/integrations/providers/azure/azure.d.ts +13 -11
  9. package/dist/plus/integrations/providers/azure/azure.js +128 -47
  10. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  11. package/dist/plus/integrations/providers/azure/models.d.ts +4 -10
  12. package/dist/plus/integrations/providers/azure/models.js +25 -9
  13. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  14. package/dist/plus/integrations/providers/azure/workItemDescriptions.d.ts +10 -0
  15. package/dist/plus/integrations/providers/azure/workItemDescriptions.js +48 -0
  16. package/dist/plus/integrations/providers/azure/workItemDescriptions.js.map +1 -0
  17. package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +20 -19
  18. package/dist/plus/integrations/providers/azure/workItemStateCategories.js +65 -40
  19. package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -1
  20. package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
  21. package/dist/plus/integrations/providers/providerApiClient.d.ts +9 -0
  22. package/dist/plus/integrations/providers/providerApiClient.js +66 -0
  23. package/dist/plus/integrations/providers/providerApiClient.js.map +1 -0
  24. package/dist/plus/integrations/providers/providersApi.d.ts +10 -9
  25. package/dist/plus/integrations/providers/providersApi.js +163 -145
  26. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  27. package/dist/plus/integrations/reads/drains.d.ts +0 -1
  28. package/docs/integrations.md +30 -13
  29. package/package.json +2 -2
@@ -1,6 +1,7 @@
1
1
  import { Disposable } from "../../../utils/disposable.js";
2
2
  import { GitHubApi } from "../../git-github/api/github.js";
3
3
  import { AzureWorkItemStatesCache } from "./azure/workItemStateCategories.js";
4
+ import { AzureWorkItemDescriptionsCache } from "./azure/workItemDescriptions.js";
4
5
  import { AzureDevOpsApi } from "./azure/azure.js";
5
6
  import { BitbucketApi } from "./bitbucket/bitbucket.js";
6
7
  import "./github/github.js";
@@ -15,6 +16,7 @@ import { IntegrationServiceContext } from "../context.js";
15
16
  */
16
17
  export interface ApiClients {
17
18
  readonly azureWorkItemStates: AzureWorkItemStatesCache;
19
+ readonly azureWorkItemDescriptions: AzureWorkItemDescriptionsCache;
18
20
  readonly github: Promise<GitHubApi | undefined>;
19
21
  readonly gitlab: Promise<GitLabApi | undefined>;
20
22
  readonly bitbucket: Promise<BitbucketApi | undefined>;
@@ -1,4 +1,5 @@
1
1
  import { getScopedLogger } from "../../../utils/logger.scoped.js";
2
+ import { AzureWorkItemDescriptionsCache } from "./azure/workItemDescriptions.js";
2
3
  import { AzureWorkItemStatesCache } from "./azure/workItemStateCategories.js";
3
4
  //#region ../plus/integrations/src/providers/apiClients.ts
4
5
  /**
@@ -8,9 +9,16 @@ import { AzureWorkItemStatesCache } from "./azure/workItemStateCategories.js";
8
9
  */
9
10
  function createApiClients(ctx, disposables) {
10
11
  const azureWorkItemStates = new AzureWorkItemStatesCache();
12
+ const azureWorkItemDescriptions = new AzureWorkItemDescriptionsCache();
11
13
  disposables.push(ctx.config.onDidChange((e) => {
12
- if (e.httpProxy) azureWorkItemStates.clear();
13
- }), { dispose: () => azureWorkItemStates.clear() });
14
+ if (e.httpProxy) {
15
+ azureWorkItemStates.clear();
16
+ azureWorkItemDescriptions.clear();
17
+ }
18
+ }), { dispose: () => {
19
+ azureWorkItemStates.clear();
20
+ azureWorkItemDescriptions.clear();
21
+ } });
14
22
  let github;
15
23
  let gitlab;
16
24
  let bitbucket;
@@ -28,6 +36,7 @@ function createApiClients(ctx, disposables) {
28
36
  };
29
37
  return {
30
38
  azureWorkItemStates,
39
+ azureWorkItemDescriptions,
31
40
  get github() {
32
41
  return github ??= build(async () => (await import(
33
42
  /* webpackChunkName: "integrations" */
@@ -50,7 +59,7 @@ function createApiClients(ctx, disposables) {
50
59
  return azure ??= build(async () => (await import(
51
60
  /* webpackChunkName: "integrations" */
52
61
  "./azure/azure.js"
53
- )).createAzureDevOpsApi(ctx, azureWorkItemStates));
62
+ )).createAzureDevOpsApi(ctx, azureWorkItemStates, azureWorkItemDescriptions));
54
63
  }
55
64
  };
56
65
  }
@@ -1 +1 @@
1
- {"version":3,"file":"apiClients.js","names":[],"sources":["../../../../../plus/integrations/src/providers/apiClients.ts"],"sourcesContent":["import type { Disposable } from '@gitlens/utils/disposable.js';\nimport { getScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport type { IntegrationServiceContext } from '../context.js';\nimport type { AzureDevOpsApi } from './azure/azure.js';\nimport { AzureWorkItemStatesCache } from './azure/workItemStateCategories.js';\nimport type { BitbucketApi } from './bitbucket/bitbucket.js';\nimport type { GitHubApi } from './github/github.js';\nimport type { GitLabApi } from './gitlab/gitlab.js';\n\n/**\n * The per-provider API clients, owned and built by the package. `GitLabApi`/`BitbucketApi`/`AzureDevOpsApi`\n * live in the package; `GitHubApi` comes from `@gitlens/git-github` (shared with the host's GitHub git\n * provider, which reads the manager's memoized instance via `manager.github`). `undefined` is tolerated for\n * offline/limited runtimes.\n */\nexport interface ApiClients {\n\treadonly azureWorkItemStates: AzureWorkItemStatesCache;\n\treadonly github: Promise<GitHubApi | undefined>;\n\treadonly gitlab: Promise<GitLabApi | undefined>;\n\treadonly bitbucket: Promise<BitbucketApi | undefined>;\n\treadonly azure: Promise<AzureDevOpsApi | undefined>;\n}\n\n/**\n * Lazily constructs + memoizes the API clients from the runtime context, pushing each to `disposables` for\n * teardown. Mirrors the lazy-chunked construction the host's `Container` did before the package took\n * ownership (the host used to build these and inject them back through the context — a pure round-trip).\n */\nexport function createApiClients(ctx: IntegrationServiceContext, disposables: Disposable[]): ApiClients {\n\tconst azureWorkItemStates = new AzureWorkItemStatesCache();\n\tdisposables.push(\n\t\tctx.config.onDidChange(e => {\n\t\t\tif (e.httpProxy) {\n\t\t\t\tazureWorkItemStates.clear();\n\t\t\t}\n\t\t}),\n\t\t{ dispose: () => azureWorkItemStates.clear() },\n\t);\n\tlet github: Promise<GitHubApi | undefined> | undefined;\n\tlet gitlab: Promise<GitLabApi | undefined> | undefined;\n\tlet bitbucket: Promise<BitbucketApi | undefined> | undefined;\n\tlet azure: Promise<AzureDevOpsApi | undefined> | undefined;\n\n\tconst build = async <T extends Disposable>(load: () => Promise<T>): Promise<T | undefined> => {\n\t\tconst scope = getScopedLogger();\n\t\ttry {\n\t\t\tconst api = await load();\n\t\t\tdisposables.push(api);\n\t\t\treturn api;\n\t\t} catch (ex) {\n\t\t\tscope?.error(ex, 'Failed to construct integration API client');\n\t\t\treturn undefined;\n\t\t}\n\t};\n\n\treturn {\n\t\tazureWorkItemStates: azureWorkItemStates,\n\t\tget github() {\n\t\t\treturn (github ??= build(async () =>\n\t\t\t\t(await import(/* webpackChunkName: \"integrations\" */ './github/github.js')).createGitHubApi(ctx),\n\t\t\t));\n\t\t},\n\t\tget gitlab() {\n\t\t\treturn (gitlab ??= build(async () =>\n\t\t\t\t(await import(/* webpackChunkName: \"integrations\" */ './gitlab/gitlab.js')).createGitLabApi(ctx),\n\t\t\t));\n\t\t},\n\t\tget bitbucket() {\n\t\t\treturn (bitbucket ??= build(async () =>\n\t\t\t\t(await import(/* webpackChunkName: \"integrations\" */ './bitbucket/bitbucket.js')).createBitbucketApi(\n\t\t\t\t\tctx,\n\t\t\t\t),\n\t\t\t));\n\t\t},\n\t\tget azure() {\n\t\t\treturn (azure ??= build(async () =>\n\t\t\t\t(await import(/* webpackChunkName: \"integrations\" */ './azure/azure.js')).createAzureDevOpsApi(\n\t\t\t\t\tctx,\n\t\t\t\t\tazureWorkItemStates,\n\t\t\t\t),\n\t\t\t));\n\t\t},\n\t};\n}\n"],"mappings":";;;;;;;;AA4BA,SAAgB,iBAAiB,KAAgC,aAAuC;CACvG,MAAM,sBAAsB,IAAI,yBAAyB;CACzD,YAAY,KACX,IAAI,OAAO,aAAY,MAAK;EAC3B,IAAI,EAAE,WACL,oBAAoB,MAAM;CAE5B,CAAC,GACD,EAAE,eAAe,oBAAoB,MAAM,EAAE,CAC9C;CACA,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CAEJ,MAAM,QAAQ,OAA6B,SAAmD;EAC7F,MAAM,QAAQ,gBAAgB;EAC9B,IAAI;GACH,MAAM,MAAM,MAAM,KAAK;GACvB,YAAY,KAAK,GAAG;GACpB,OAAO;EACR,SAAS,IAAI;GACZ,OAAO,MAAM,IAAI,4CAA4C;GAC7D;EACD;CACD;CAEA,OAAO;EACe;EACrB,IAAI,SAAS;GACZ,OAAQ,WAAW,MAAM,aACvB,MAAM;;IAA8C;EAAqB,CAAE,gBAAgB,GAAG,CAChG;EACD;EACA,IAAI,SAAS;GACZ,OAAQ,WAAW,MAAM,aACvB,MAAM;;IAA8C;EAAqB,CAAE,gBAAgB,GAAG,CAChG;EACD;EACA,IAAI,YAAY;GACf,OAAQ,cAAc,MAAM,aAC1B,MAAM;;IAA8C;EAA2B,CAAE,mBACjF,GACD,CACD;EACD;EACA,IAAI,QAAQ;GACX,OAAQ,UAAU,MAAM,aACtB,MAAM;;IAA8C;EAAmB,CAAE,qBACzE,KACA,mBACD,CACD;EACD;CACD;AACD"}
1
+ {"version":3,"file":"apiClients.js","names":[],"sources":["../../../../../plus/integrations/src/providers/apiClients.ts"],"sourcesContent":["import type { Disposable } from '@gitlens/utils/disposable.js';\nimport { getScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport type { IntegrationServiceContext } from '../context.js';\nimport type { AzureDevOpsApi } from './azure/azure.js';\nimport { AzureWorkItemDescriptionsCache } from './azure/workItemDescriptions.js';\nimport { AzureWorkItemStatesCache } from './azure/workItemStateCategories.js';\nimport type { BitbucketApi } from './bitbucket/bitbucket.js';\nimport type { GitHubApi } from './github/github.js';\nimport type { GitLabApi } from './gitlab/gitlab.js';\n\n/**\n * The per-provider API clients, owned and built by the package. `GitLabApi`/`BitbucketApi`/`AzureDevOpsApi`\n * live in the package; `GitHubApi` comes from `@gitlens/git-github` (shared with the host's GitHub git\n * provider, which reads the manager's memoized instance via `manager.github`). `undefined` is tolerated for\n * offline/limited runtimes.\n */\nexport interface ApiClients {\n\treadonly azureWorkItemStates: AzureWorkItemStatesCache;\n\treadonly azureWorkItemDescriptions: AzureWorkItemDescriptionsCache;\n\treadonly github: Promise<GitHubApi | undefined>;\n\treadonly gitlab: Promise<GitLabApi | undefined>;\n\treadonly bitbucket: Promise<BitbucketApi | undefined>;\n\treadonly azure: Promise<AzureDevOpsApi | undefined>;\n}\n\n/**\n * Lazily constructs + memoizes the API clients from the runtime context, pushing each to `disposables` for\n * teardown. Mirrors the lazy-chunked construction the host's `Container` did before the package took\n * ownership (the host used to build these and inject them back through the context — a pure round-trip).\n */\nexport function createApiClients(ctx: IntegrationServiceContext, disposables: Disposable[]): ApiClients {\n\tconst azureWorkItemStates = new AzureWorkItemStatesCache();\n\tconst azureWorkItemDescriptions = new AzureWorkItemDescriptionsCache();\n\tdisposables.push(\n\t\tctx.config.onDidChange(e => {\n\t\t\tif (e.httpProxy) {\n\t\t\t\tazureWorkItemStates.clear();\n\t\t\t\tazureWorkItemDescriptions.clear();\n\t\t\t}\n\t\t}),\n\t\t{\n\t\t\tdispose: () => {\n\t\t\t\tazureWorkItemStates.clear();\n\t\t\t\tazureWorkItemDescriptions.clear();\n\t\t\t},\n\t\t},\n\t);\n\tlet github: Promise<GitHubApi | undefined> | undefined;\n\tlet gitlab: Promise<GitLabApi | undefined> | undefined;\n\tlet bitbucket: Promise<BitbucketApi | undefined> | undefined;\n\tlet azure: Promise<AzureDevOpsApi | undefined> | undefined;\n\n\tconst build = async <T extends Disposable>(load: () => Promise<T>): Promise<T | undefined> => {\n\t\tconst scope = getScopedLogger();\n\t\ttry {\n\t\t\tconst api = await load();\n\t\t\tdisposables.push(api);\n\t\t\treturn api;\n\t\t} catch (ex) {\n\t\t\tscope?.error(ex, 'Failed to construct integration API client');\n\t\t\treturn undefined;\n\t\t}\n\t};\n\n\treturn {\n\t\tazureWorkItemStates: azureWorkItemStates,\n\t\tazureWorkItemDescriptions: azureWorkItemDescriptions,\n\t\tget github() {\n\t\t\treturn (github ??= build(async () =>\n\t\t\t\t(await import(/* webpackChunkName: \"integrations\" */ './github/github.js')).createGitHubApi(ctx),\n\t\t\t));\n\t\t},\n\t\tget gitlab() {\n\t\t\treturn (gitlab ??= build(async () =>\n\t\t\t\t(await import(/* webpackChunkName: \"integrations\" */ './gitlab/gitlab.js')).createGitLabApi(ctx),\n\t\t\t));\n\t\t},\n\t\tget bitbucket() {\n\t\t\treturn (bitbucket ??= build(async () =>\n\t\t\t\t(await import(/* webpackChunkName: \"integrations\" */ './bitbucket/bitbucket.js')).createBitbucketApi(\n\t\t\t\t\tctx,\n\t\t\t\t),\n\t\t\t));\n\t\t},\n\t\tget azure() {\n\t\t\treturn (azure ??= build(async () =>\n\t\t\t\t(await import(/* webpackChunkName: \"integrations\" */ './azure/azure.js')).createAzureDevOpsApi(\n\t\t\t\t\tctx,\n\t\t\t\t\tazureWorkItemStates,\n\t\t\t\t\tazureWorkItemDescriptions,\n\t\t\t\t),\n\t\t\t));\n\t\t},\n\t};\n}\n"],"mappings":";;;;;;;;;AA8BA,SAAgB,iBAAiB,KAAgC,aAAuC;CACvG,MAAM,sBAAsB,IAAI,yBAAyB;CACzD,MAAM,4BAA4B,IAAI,+BAA+B;CACrE,YAAY,KACX,IAAI,OAAO,aAAY,MAAK;EAC3B,IAAI,EAAE,WAAW;GAChB,oBAAoB,MAAM;GAC1B,0BAA0B,MAAM;EACjC;CACD,CAAC,GACD,EACC,eAAe;EACd,oBAAoB,MAAM;EAC1B,0BAA0B,MAAM;CACjC,EACD,CACD;CACA,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CAEJ,MAAM,QAAQ,OAA6B,SAAmD;EAC7F,MAAM,QAAQ,gBAAgB;EAC9B,IAAI;GACH,MAAM,MAAM,MAAM,KAAK;GACvB,YAAY,KAAK,GAAG;GACpB,OAAO;EACR,SAAS,IAAI;GACZ,OAAO,MAAM,IAAI,4CAA4C;GAC7D;EACD;CACD;CAEA,OAAO;EACe;EACM;EAC3B,IAAI,SAAS;GACZ,OAAQ,WAAW,MAAM,aACvB,MAAM;;IAA8C;EAAqB,CAAE,gBAAgB,GAAG,CAChG;EACD;EACA,IAAI,SAAS;GACZ,OAAQ,WAAW,MAAM,aACvB,MAAM;;IAA8C;EAAqB,CAAE,gBAAgB,GAAG,CAChG;EACD;EACA,IAAI,YAAY;GACf,OAAQ,cAAc,MAAM,aAC1B,MAAM;;IAA8C;EAA2B,CAAE,mBACjF,GACD,CACD;EACD;EACA,IAAI,QAAQ;GACX,OAAQ,UAAU,MAAM,aACtB,MAAM;;IAA8C;EAAmB,CAAE,qBACzE,KACA,qBACA,yBACD,CACD;EACD;CACD;AACD"}
@@ -9,6 +9,7 @@ import { RepositoryMetadata } from "../../../../git/models/repositoryMetadata.js
9
9
  import { TokenWithInfo } from "../../authentication/models.js";
10
10
  import { AzureProjectDescriptor, AzureWorkItemStateCategory } from "./models.js";
11
11
  import { AzureWorkItemStatesCache } from "./workItemStateCategories.js";
12
+ import { AzureWorkItemDescriptionsCache } from "./workItemDescriptions.js";
12
13
  import { ProviderIssueSearchPage } from "../../models/issueReads.js";
13
14
  import { ProviderApiConfig } from "../apiConfig.js";
14
15
  import { IntegrationServiceContext } from "../../context.js";
@@ -21,9 +22,12 @@ type WorkItemSearchOptions = {
21
22
  };
22
23
  export declare class AzureDevOpsApi implements Disposable {
23
24
  private readonly config;
25
+ private readonly restApi;
24
26
  private readonly _disposable;
25
27
  private readonly _workItemStates;
26
28
  private readonly ownsWorkItemStates;
29
+ private readonly _workItemDescriptions;
30
+ private readonly ownsWorkItemDescriptions;
27
31
  private readonly _forkRepositoryUrls;
28
32
  /**
29
33
  * The ordered ids each filtered work-item search's first page queried, paged through by its continuations. Kept
@@ -31,7 +35,7 @@ export declare class AzureDevOpsApi implements Disposable {
31
35
  * the capacity leaves room for repeated first pages before an idle pagination's snapshot is the one evicted.
32
36
  */
33
37
  private readonly _workItemSearches;
34
- constructor(config: ProviderApiConfig, workItemStates?: AzureWorkItemStatesCache);
38
+ constructor(config: ProviderApiConfig, workItemStates?: AzureWorkItemStatesCache, workItemDescriptions?: AzureWorkItemDescriptionsCache);
35
39
  dispose(): void;
36
40
  private resetCaches;
37
41
  getPullRequestForBranch(provider: Provider, token: TokenWithInfo, owner: string, repo: string, branch: string, options: {
@@ -76,6 +80,7 @@ export declare class AzureDevOpsApi implements Disposable {
76
80
  getWorkItemStateCategory(issueType: string, state: string, provider: Provider, token: TokenWithInfo, owner: string, projectName: string, options: {
77
81
  baseUrl: string;
78
82
  }, cancellation?: AbortSignal): Promise<AzureWorkItemStateCategory | undefined>;
83
+ private getWorkItemTypeStates;
79
84
  getRepositoryMetadata(provider: Provider, token: TokenWithInfo, owner: string, repo: string, options: {
80
85
  baseUrl: string;
81
86
  }, cancellation?: AbortSignal): Promise<RepositoryMetadata | undefined>;
@@ -118,19 +123,11 @@ export declare class AzureDevOpsApi implements Disposable {
118
123
  /**
119
124
  * The ids a collection-level WIQL query matches, in its order.
120
125
  *
121
- * `api-version=5.0` is the oldest version every Azure DevOps Server release this search supports (2019 and later)
122
- * answers; `timePrecision=true` is what lets the criteria's dates compare as instants — see `toWiqlDate`.
126
+ * `timePrecision=true` lets the criteria's dates compare as instants, including on legacy servers.
123
127
  */
124
128
  private queryWorkItemIds;
125
129
  private getRepository;
126
130
  private toPullRequest;
127
- /**
128
- * The organization's rich text fields a work item's description may come from, for a work item none of the
129
- * built-in description fields describes. Best-effort: a failed read leaves the description to those fields rather
130
- * than failing the work item, so it is `undefined` on any failure but a cancellation. Logs to the caller's `scope`,
131
- * captured before its first `await`: a scope read here, after the caller's reads, can be stale in a browser.
132
- */
133
- private getWorkItemHtmlFields;
134
131
  /**
135
132
  * Resolves the web and HTTPS clone URLs of the fork a cross-repository pull request comes from. Every other
136
133
  * repository URL the model reports is rebuilt from the pull request payload; only a fork reference lacks the
@@ -142,10 +139,15 @@ export declare class AzureDevOpsApi implements Disposable {
142
139
  */
143
140
  private getForkRepositoryUrls;
144
141
  private fetchForkRepositoryUrls;
142
+ private getWorkItem;
143
+ private requireWorkItemProject;
144
+ private getWorkItemHtmlFields;
145
+ private getRestResource;
146
+ private requireRestResource;
145
147
  private request;
146
148
  private handleRequestError;
147
149
  }
148
150
  /** Wires an {@link AzureDevOpsApi} from the full runtime context, mapping `ctx` down to the narrow config. */
149
- export declare function createAzureDevOpsApi(ctx: IntegrationServiceContext, workItemStates?: AzureWorkItemStatesCache): AzureDevOpsApi;
151
+ export declare function createAzureDevOpsApi(ctx: IntegrationServiceContext, workItemStates?: AzureWorkItemStatesCache, workItemDescriptions?: AzureWorkItemDescriptionsCache): AzureDevOpsApi;
150
152
  //#endregion
151
153
  //# sourceMappingURL=azure.d.ts.map
@@ -1,4 +1,4 @@
1
- import { CancellationError, isCancellationError } from "../../../../utils/cancellation.js";
1
+ import { CancellationError, isCancellationError, raceWithSignal } from "../../../../utils/cancellation.js";
2
2
  import { Logger as _logger } from "../../../../utils/logger.js";
3
3
  import { getScopedLogger } from "../../../../utils/logger.scoped.js";
4
4
  import { mapBounded } from "../../../../utils/promise.js";
@@ -11,14 +11,16 @@ import { base64 } from "../../../../utils/base64.js";
11
11
  import { sha256, uuid } from "../../../../utils/crypto.js";
12
12
  import { effectiveIssueSort } from "../../../../git/utils/issue.utils.js";
13
13
  import { ProviderFetchError, isRateLimitResponse, toRateLimitError } from "../../errors.js";
14
+ import { throwProviderError } from "../providerErrors.js";
14
15
  import { decodePathSegment } from "../../utils/domain.utils.js";
15
16
  import { azureWorkItemSearchResultLimit, parseAzureWorkItemSearchCursor, toAzureSearchPageSize, toAzureWorkItemSearchCursorKey, toAzureWorkItemSearchQueries, toAzureWorkItemSearchWiql } from "./search.js";
16
17
  import { azurePullRequestStatusToState, azureWorkItemsStateCategoryToState, encodeAzurePathSegment, fromAzurePullRequest, fromAzureWorkItem, getAzurePullRequestWebUrl, isClosedAzurePullRequestStatus, normalizeAzureBranchName, parseAzureWorkItemDate, sanitizeAzureRepositoryUrl } from "./models.js";
17
- import { AzureWorkItemStatesCache, getAzureWorkItemFieldsPath, getAzureWorkItemTypeStatesPath, getAzureWorkItemTypesUrl } from "./workItemStateCategories.js";
18
+ import { AzureWorkItemDescriptionsCache } from "./workItemDescriptions.js";
19
+ import { AzureWorkItemStatesCache, getAzureWorkItemTypeStatesPath, getAzureWorkItemTypesUrl, requireAzureWorkItemStateCategories, resolveAzureWorkItemStateCategory } from "./workItemStateCategories.js";
18
20
  import { baseProviderApiConfig } from "../apiConfig.js";
21
+ import { createProviderApiRequest, createProviderApis } from "../providerApiClient.js";
19
22
  import { queryAzureWorkItemSearch } from "./workItemSearch.js";
20
23
  import * as l10n from "@vscode/l10n";
21
- import { needsAzureWorkItemHtmlFields } from "@gitkraken/provider-apis";
22
24
  //#region ../plus/integrations/src/providers/azure/azure.ts
23
25
  const forkRepositoryUrlCacheTtl = 3e5;
24
26
  const workItemMetadataConcurrency = 6;
@@ -40,9 +42,12 @@ function parseAzureRepositoryDescriptor(repo) {
40
42
  }
41
43
  var AzureDevOpsApi = class {
42
44
  config;
45
+ restApi;
43
46
  _disposable;
44
47
  _workItemStates;
45
48
  ownsWorkItemStates;
49
+ _workItemDescriptions;
50
+ ownsWorkItemDescriptions;
46
51
  _forkRepositoryUrls = new PromiseCache({
47
52
  capacity: 100,
48
53
  createTTL: forkRepositoryUrlCacheTtl
@@ -57,17 +62,22 @@ var AzureDevOpsApi = class {
57
62
  accessTTL: workItemSearchSnapshotTtl,
58
63
  createTTL: workItemSearchSnapshotMaxTtl
59
64
  });
60
- constructor(config, workItemStates) {
65
+ constructor(config, workItemStates, workItemDescriptions) {
61
66
  this.config = config;
67
+ this.restApi = createProviderApis({ request: createProviderApiRequest((input, init) => config.fetch(input, init)) }).azureDevOps;
62
68
  this._workItemStates = workItemStates ?? new AzureWorkItemStatesCache();
63
69
  this.ownsWorkItemStates = workItemStates == null;
70
+ this._workItemDescriptions = workItemDescriptions ?? new AzureWorkItemDescriptionsCache();
71
+ this.ownsWorkItemDescriptions = workItemDescriptions == null;
64
72
  this._disposable = config.onConfigChanged?.(() => this.resetCaches());
65
73
  }
66
74
  dispose() {
67
75
  this._disposable?.dispose();
68
76
  }
69
77
  resetCaches() {
78
+ this.restApi.updateConfig({});
70
79
  if (this.ownsWorkItemStates) this._workItemStates.clear();
80
+ if (this.ownsWorkItemDescriptions) this._workItemDescriptions.clear();
71
81
  this._forkRepositoryUrls.clear();
72
82
  this._workItemSearches.clear();
73
83
  }
@@ -92,8 +102,11 @@ var AzureDevOpsApi = class {
92
102
  async getPullRequestForCommit(provider, token, owner, repo, rev, baseUrl, _options, cancellation) {
93
103
  const scope = getScopedLogger();
94
104
  const { projectName, repoName } = parseAzureRepositoryDescriptor(repo);
105
+ const resource = await this.requireRestResource(provider, token, owner, baseUrl, "pullRequestQuery", "4.1", cancellation);
95
106
  try {
96
- const pr = (await this.request(provider, token, baseUrl, `${encodeAzurePathSegment(owner)}/${encodeAzurePathSegment(projectName)}/_apis/git/repositories/${encodeAzurePathSegment(repoName)}/pullrequestquery?api-version=4.1`, {
107
+ const encodedProject = encodeAzurePathSegment(projectName);
108
+ const encodedRepo = encodeAzurePathSegment(repoName);
109
+ const pr = (await this.request(provider, token, baseUrl, `${encodeAzurePathSegment(owner)}/${encodedProject}/_apis/git/repositories/${encodedRepo}/pullrequestquery?api-version=${resource.apiVersion}`, {
97
110
  method: "POST",
98
111
  body: JSON.stringify({ queries: [{
99
112
  items: [rev],
@@ -125,9 +138,10 @@ var AzureDevOpsApi = class {
125
138
  const scope = getScopedLogger();
126
139
  const { projectName, repoName } = parseAzureRepositoryDescriptor(repo);
127
140
  if (options?.type === void 0 || options?.type === "issue") {
141
+ const resource = await this.requireRestResource(provider, token, owner, options.baseUrl, "workItems", "6.0");
128
142
  let issueResult;
129
143
  try {
130
- issueResult = await this.request(provider, token, options?.baseUrl, `${encodeAzurePathSegment(owner)}/${encodeAzurePathSegment(projectName)}/_apis/wit/workItems/${encodeAzurePathSegment(id)}`, { method: "GET" }, scope);
144
+ issueResult = await this.getWorkItem(provider, token, owner, projectName, id, options.baseUrl, resource, scope);
131
145
  } catch (ex) {
132
146
  if (ex instanceof AuthenticationError) throw ex;
133
147
  if (ex.original?.status !== 404) {
@@ -136,6 +150,7 @@ var AzureDevOpsApi = class {
136
150
  }
137
151
  }
138
152
  if (issueResult != null) {
153
+ await this.requireWorkItemProject(provider, token, owner, projectName, issueResult, options.baseUrl, scope);
139
154
  const issueType = issueResult.fields["System.WorkItemType"];
140
155
  const state = issueResult.fields["System.State"];
141
156
  const stateCategory = await this.getWorkItemStateCategory(issueType, state, provider, token, owner, projectName, options);
@@ -179,9 +194,10 @@ var AzureDevOpsApi = class {
179
194
  }
180
195
  async getIssue(provider, token, project, id, options) {
181
196
  const scope = getScopedLogger();
197
+ const resource = await this.requireRestResource(provider, token, project.resourceName, options.baseUrl, "workItems", "6.0");
182
198
  let issueResult;
183
199
  try {
184
- issueResult = await this.request(provider, token, options?.baseUrl, `${encodeAzurePathSegment(project.resourceName)}/${encodeAzurePathSegment(project.name)}/_apis/wit/workItems/${encodeAzurePathSegment(id)}`, { method: "GET" }, scope);
200
+ issueResult = await this.getWorkItem(provider, token, project.resourceName, project.name, id, options.baseUrl, resource, scope);
185
201
  } catch (ex) {
186
202
  if (ex instanceof AuthenticationError) throw ex;
187
203
  if (ex.original?.status !== 404) {
@@ -190,9 +206,10 @@ var AzureDevOpsApi = class {
190
206
  }
191
207
  }
192
208
  if (issueResult != null) {
209
+ await this.requireWorkItemProject(provider, token, project.resourceName, project.name, issueResult, options.baseUrl, scope);
193
210
  const issueType = issueResult.fields["System.WorkItemType"];
194
211
  const state = issueResult.fields["System.State"];
195
- const [stateCategory, htmlFields] = await Promise.all([this.getWorkItemStateCategory(issueType, state, provider, token, project.resourceName, project.name, options), needsAzureWorkItemHtmlFields(issueResult.fields) ? this.getWorkItemHtmlFields(provider, token, options.baseUrl, project.resourceName, scope) : void 0]);
212
+ const [stateCategory, htmlFields] = await Promise.all([this.getWorkItemStateCategory(issueType, state, provider, token, project.resourceName, project.name, options), this.getWorkItemHtmlFields(provider, token, project.resourceName, options.baseUrl, issueResult.fields, scope)]);
196
213
  return fromAzureWorkItem(issueResult, provider, project, stateCategory, "point", htmlFields);
197
214
  }
198
215
  }
@@ -258,18 +275,30 @@ var AzureDevOpsApi = class {
258
275
  }
259
276
  }
260
277
  async getWorkItemStateCategory(issueType, state, provider, token, owner, projectName, options, cancellation) {
261
- return (await this._workItemStates.getStates({
262
- baseUrl: options.baseUrl,
278
+ const states = await this.getWorkItemTypeStates(provider, token, owner, projectName, issueType, options.baseUrl, cancellation);
279
+ return resolveAzureWorkItemStateCategory(states, state);
280
+ }
281
+ getWorkItemTypeStates(provider, token, owner, project, type, baseUrl, cancellation) {
282
+ return this._workItemStates.getStates({
283
+ baseUrl,
263
284
  namespace: owner,
264
- project: projectName,
285
+ project,
265
286
  token: token.accessToken,
266
287
  isPAT: true
267
- }, issueType, (signal) => this.request(provider, token, options.baseUrl, getAzureWorkItemTypeStatesPath(owner, projectName, issueType), { method: "GET" }, void 0, signal), cancellation)).find((s) => s.name === state)?.category;
288
+ }, type, async (signal) => {
289
+ const resource = await this.requireRestResource(provider, token, owner, baseUrl, "workItemTypeStates", "6.0-preview.1", signal);
290
+ return this.request(provider, token, baseUrl, getAzureWorkItemTypeStatesPath(owner, project, type, resource.apiVersion), { method: "GET" }, void 0, signal);
291
+ }, cancellation, async (signal) => {
292
+ const resource = await this.requireRestResource(provider, token, owner, baseUrl, "backlogConfiguration", "6.0-preview.1", signal);
293
+ const path = `${encodeAzurePathSegment(owner)}/${encodeAzurePathSegment(project)}/_apis/work/backlogconfiguration?api-version=${resource.apiVersion}`;
294
+ return this.request(provider, token, baseUrl, path, { method: "GET" }, void 0, signal);
295
+ });
268
296
  }
269
297
  async getRepositoryMetadata(provider, token, owner, repo, options, cancellation) {
270
298
  const scope = getScopedLogger();
299
+ const resource = await this.requireRestResource(provider, token, owner, options.baseUrl, "repositories", "7.1", cancellation);
271
300
  try {
272
- const response = await this.getRepository(provider, token, owner, repo, options.baseUrl, scope, cancellation);
301
+ const response = await this.getRepository(provider, token, owner, repo, options.baseUrl, resource.apiVersion, scope, cancellation);
273
302
  if (response == null) return void 0;
274
303
  return {
275
304
  provider,
@@ -289,8 +318,9 @@ var AzureDevOpsApi = class {
289
318
  }
290
319
  async getDefaultBranch(provider, token, owner, repo, options, cancellation) {
291
320
  const scope = getScopedLogger();
321
+ const resource = await this.requireRestResource(provider, token, owner, options.baseUrl, "repositories", "7.1", cancellation);
292
322
  try {
293
- const response = await this.getRepository(provider, token, owner, repo, options.baseUrl, scope, cancellation);
323
+ const response = await this.getRepository(provider, token, owner, repo, options.baseUrl, resource.apiVersion, scope, cancellation);
294
324
  if (response?.defaultBranch == null) return void 0;
295
325
  return {
296
326
  provider,
@@ -353,24 +383,28 @@ var AzureDevOpsApi = class {
353
383
  const slice = ids.slice(offset, offset + pageSize);
354
384
  const values = [];
355
385
  if (slice.length > 0) {
356
- const workItems = await this.request(provider, token, options.baseUrl, `${encodeAzurePathSegment(collection)}/_apis/wit/workitemsbatch?api-version=5.0`, {
386
+ const batch = await this.getRestResource(provider, token, collection, options.baseUrl, "workItemsBatch", "5.0", cancellation);
387
+ const resource = batch ?? await this.requireRestResource(provider, token, collection, options.baseUrl, "workItems", "5.0", cancellation);
388
+ const route = batch != null ? `workitemsbatch?api-version=${resource.apiVersion}` : `workitems?ids=${slice.join(",")}&$expand=Links&errorPolicy=Omit&api-version=${resource.apiVersion}`;
389
+ const workItems = await this.request(provider, token, options.baseUrl, `${encodeAzurePathSegment(collection)}/_apis/wit/${route}`, batch != null ? {
357
390
  method: "POST",
358
391
  body: JSON.stringify({
359
392
  ids: slice,
360
393
  $expand: "Links",
361
394
  errorPolicy: "Omit"
362
395
  })
363
- }, scope, cancellation);
396
+ } : { method: "GET" }, scope, cancellation);
364
397
  const byId = new Map((workItems?.value ?? []).filter((w) => w != null).map((w) => [w.id, w]));
365
- const htmlFields = [...byId.values()].some((w) => needsAzureWorkItemHtmlFields(w.fields)) ? await this.getWorkItemHtmlFields(provider, token, options.baseUrl, collection, scope, cancellation) : void 0;
366
398
  for (const id of slice) {
367
399
  const workItem = byId.get(id);
368
400
  if (workItem == null) continue;
369
401
  const projectName = workItem.fields["System.TeamProject"];
402
+ if (options.projectNames?.length && !options.projectNames.some((name) => name.toLowerCase() === projectName?.toLowerCase())) throw new Error(`Azure DevOps work item ${id} is outside the requested projects`);
370
403
  const project = options.resolveProject(projectName);
371
404
  if (project == null) throw new Error(`Azure DevOps work item ${id} is in project '${projectName}', which isn't visible`);
372
405
  if (cancellation?.aborted) throw new CancellationError();
373
406
  const stateCategory = await this.getWorkItemStateCategory(workItem.fields["System.WorkItemType"], workItem.fields["System.State"], provider, token, collection, project.name, options, cancellation);
407
+ const htmlFields = await this.getWorkItemHtmlFields(provider, token, collection, options.baseUrl, workItem.fields, scope, cancellation);
374
408
  values.push(fromAzureWorkItem(workItem, provider, project, stateCategory, "search", htmlFields));
375
409
  }
376
410
  }
@@ -428,7 +462,10 @@ var AzureDevOpsApi = class {
428
462
  token: token.accessToken,
429
463
  isPAT: true
430
464
  };
431
- return (await this._workItemStates.getTypes(scope, (signal) => this.request(provider, token, void 0, getAzureWorkItemTypesUrl(options.baseUrl, collection, project), { method: "GET" }, void 0, signal), cancellation)).map((name) => ({
465
+ return (await this._workItemStates.getTypes(scope, async (signal) => {
466
+ const resource = await this.requireRestResource(provider, token, collection, options.baseUrl, "workItemTypes", "6.0", signal);
467
+ return this.request(provider, token, void 0, getAzureWorkItemTypesUrl(options.baseUrl, collection, project, resource.apiVersion), { method: "GET" }, void 0, signal);
468
+ }, cancellation)).map((name) => ({
432
469
  project,
433
470
  name,
434
471
  scope
@@ -437,7 +474,7 @@ var AzureDevOpsApi = class {
437
474
  const states = await mapBounded(types, workItemMetadataConcurrency, async (type) => ({
438
475
  name: type.name,
439
476
  project: type.project,
440
- states: await this._workItemStates.getStates(type.scope, type.name, (signal) => this.request(provider, token, options.baseUrl, getAzureWorkItemTypeStatesPath(collection, type.project, type.name), { method: "GET" }, void 0, signal), cancellation)
477
+ states: requireAzureWorkItemStateCategories(await this.getWorkItemTypeStates(provider, token, collection, type.project, type.name, options.baseUrl, cancellation))
441
478
  }));
442
479
  workflows = new Map(projectNames.map((project) => [project, { types: states.filter((type) => type.project === project) }]));
443
480
  }
@@ -446,13 +483,13 @@ var AzureDevOpsApi = class {
446
483
  /**
447
484
  * The ids a collection-level WIQL query matches, in its order.
448
485
  *
449
- * `api-version=5.0` is the oldest version every Azure DevOps Server release this search supports (2019 and later)
450
- * answers; `timePrecision=true` is what lets the criteria's dates compare as instants — see `toWiqlDate`.
486
+ * `timePrecision=true` lets the criteria's dates compare as instants, including on legacy servers.
451
487
  */
452
488
  async queryWorkItemIds(provider, token, collection, baseUrl, wiql, top, scope, cancellation, asOf) {
453
489
  const query = asOf == null ? wiql : `${wiql} ASOF '${asOf}'`;
454
490
  if (query.length > 32e3) throw new Error("Azure DevOps work item search query is too long; narrow the search");
455
- const result = await this.request(provider, token, baseUrl, `${encodeAzurePathSegment(collection)}/_apis/wit/wiql?$top=${top}&timePrecision=true&api-version=5.0`, {
491
+ const resource = await this.requireRestResource(provider, token, collection, baseUrl, "wiql", "5.0", cancellation);
492
+ const result = await this.request(provider, token, baseUrl, `${encodeAzurePathSegment(collection)}/_apis/wit/wiql?$top=${top}&timePrecision=true&api-version=${resource.apiVersion}`, {
456
493
  method: "POST",
457
494
  body: JSON.stringify({ query })
458
495
  }, scope, cancellation);
@@ -462,35 +499,17 @@ var AzureDevOpsApi = class {
462
499
  asOf: result.asOf
463
500
  };
464
501
  }
465
- getRepository(provider, token, owner, repo, baseUrl, scope, cancellation) {
502
+ async getRepository(provider, token, owner, repo, baseUrl, apiVersion, scope, cancellation) {
466
503
  const { projectName, repoName } = parseAzureRepositoryDescriptor(repo);
467
- return this.request(provider, token, baseUrl, `${encodeAzurePathSegment(owner)}/${encodeAzurePathSegment(projectName)}/_apis/git/repositories/${encodeAzurePathSegment(repoName)}?api-version=7.1`, { method: "GET" }, scope, cancellation);
504
+ const encodedProject = encodeAzurePathSegment(projectName);
505
+ const encodedRepo = encodeAzurePathSegment(repoName);
506
+ return this.request(provider, token, baseUrl, `${encodeAzurePathSegment(owner)}/${encodedProject}/_apis/git/repositories/${encodedRepo}?api-version=${apiVersion}`, { method: "GET" }, scope, cancellation);
468
507
  }
469
508
  async toPullRequest(pr, provider, token, owner, baseUrl, scope, cancellation) {
470
509
  const forkRepositoryUrls = await this.getForkRepositoryUrls(provider, token, owner, baseUrl, pr.forkSource?.repository, scope, cancellation);
471
510
  return fromAzurePullRequest(pr, provider, owner, baseUrl, forkRepositoryUrls, "point");
472
511
  }
473
512
  /**
474
- * The organization's rich text fields a work item's description may come from, for a work item none of the
475
- * built-in description fields describes. Best-effort: a failed read leaves the description to those fields rather
476
- * than failing the work item, so it is `undefined` on any failure but a cancellation. Logs to the caller's `scope`,
477
- * captured before its first `await`: a scope read here, after the caller's reads, can be stale in a browser.
478
- */
479
- async getWorkItemHtmlFields(provider, token, baseUrl, organization, scope, cancellation) {
480
- try {
481
- return await this._workItemStates.getHtmlFields({
482
- baseUrl,
483
- namespace: organization,
484
- token: token.accessToken,
485
- isPAT: true
486
- }, (signal) => this.request(provider, token, baseUrl, getAzureWorkItemFieldsPath(organization), { method: "GET" }, void 0, signal), cancellation);
487
- } catch (ex) {
488
- if (isCancellationError(ex) && cancellation?.aborted) throw ex;
489
- scope?.warn(`Unable to read the Azure DevOps field catalog of '${organization}': ${ex}`);
490
- return;
491
- }
492
- }
493
- /**
494
513
  * Resolves the web and HTTPS clone URLs of the fork a cross-repository pull request comes from. Every other
495
514
  * repository URL the model reports is rebuilt from the pull request payload; only a fork reference lacks the
496
515
  * project to do that, so only a fork costs a request.
@@ -517,7 +536,9 @@ var AzureDevOpsApi = class {
517
536
  }
518
537
  async fetchForkRepositoryUrls(provider, token, owner, baseUrl, repositoryId, cacheable, scope, cancellation) {
519
538
  try {
520
- const response = await this.request(provider, token, baseUrl, `${encodeAzurePathSegment(owner)}/_apis/git/repositories/${encodeAzurePathSegment(repositoryId)}?api-version=4.1`, { method: "GET" }, scope, cancellation);
539
+ encodeAzurePathSegment(repositoryId);
540
+ const resource = await this.requireRestResource(provider, token, owner, baseUrl, "repositories", "4.1", cancellation);
541
+ const response = await this.request(provider, token, baseUrl, `${encodeAzurePathSegment(owner)}/_apis/git/repositories/${encodeAzurePathSegment(repositoryId)}?api-version=${resource.apiVersion}`, { method: "GET" }, scope, cancellation);
521
542
  const webUrl = sanitizeAzureRepositoryUrl(response?.webUrl, baseUrl, owner);
522
543
  const cloneHttps = sanitizeAzureRepositoryUrl(response?.remoteUrl, baseUrl, owner);
523
544
  const url = webUrl ?? cloneHttps;
@@ -536,6 +557,66 @@ var AzureDevOpsApi = class {
536
557
  return;
537
558
  }
538
559
  }
560
+ async getWorkItem(provider, token, owner, project, id, baseUrl, resource, scope) {
561
+ const encodedProject = encodeAzurePathSegment(project);
562
+ const encodedId = encodeAzurePathSegment(id);
563
+ const projectPath = resource.projectScoped ? `/${encodedProject}` : "";
564
+ return this.request(provider, token, baseUrl, `${encodeAzurePathSegment(owner)}${projectPath}/_apis/wit/workItems/${encodedId}?api-version=${resource.apiVersion}`, { method: "GET" }, scope);
565
+ }
566
+ async requireWorkItemProject(provider, token, owner, project, workItem, baseUrl, scope) {
567
+ const actualProject = workItem.fields?.["System.TeamProject"];
568
+ if (typeof actualProject !== "string" || !actualProject.trim()) throw new Error(`Azure DevOps work item ${workItem.id} has no project`);
569
+ if (actualProject.toLowerCase() === project.toLowerCase()) return;
570
+ if ((await this.request(provider, token, baseUrl, `${encodeAzurePathSegment(owner)}/_apis/projects/${encodeAzurePathSegment(project)}`, { method: "GET" }, scope))?.name?.toLowerCase() !== actualProject.toLowerCase()) throw new Error(`Azure DevOps work item ${workItem.id} is outside the requested project`);
571
+ }
572
+ async getWorkItemHtmlFields(provider, token, namespace, baseUrl, fields, scope, cancellation) {
573
+ try {
574
+ const requestToken = { ...token };
575
+ return await this._workItemDescriptions.getFields(fields, {
576
+ baseUrl,
577
+ namespace,
578
+ token: requestToken.accessToken,
579
+ isPAT: true
580
+ }, async (signal) => {
581
+ const resource = await this.requireRestResource(provider, requestToken, namespace, baseUrl, "fields", "6.0", signal);
582
+ return this.request(provider, requestToken, baseUrl, `${encodeAzurePathSegment(namespace)}/_apis/wit/fields?api-version=${resource.apiVersion}`, { method: "GET" }, void 0, signal);
583
+ }, cancellation);
584
+ } catch (ex) {
585
+ if (isCancellationError(ex) && cancellation?.aborted) throw ex;
586
+ scope?.warn(`Unable to read the Azure DevOps field catalog of '${namespace}': ${ex}`);
587
+ return [];
588
+ }
589
+ }
590
+ async getRestResource(provider, token, namespace, baseUrl, resource, version, cancellation) {
591
+ try {
592
+ if (cancellation?.aborted) throw new CancellationError();
593
+ encodeAzurePathSegment(namespace);
594
+ const discovery = this.config.wrapForForcedInsecureSSL(provider.getIgnoreSSLErrors(), () => this.restApi.getRestApiResource({
595
+ namespace,
596
+ resource,
597
+ version
598
+ }, {
599
+ token: token.accessToken,
600
+ isPAT: true,
601
+ baseUrl
602
+ }));
603
+ if (cancellation?.aborted) discovery.catch(() => {});
604
+ return await (cancellation != null ? raceWithSignal(discovery, cancellation) : discovery);
605
+ } catch (ex) {
606
+ return throwProviderError(token, ex);
607
+ }
608
+ }
609
+ async requireRestResource(provider, token, namespace, baseUrl, resource, version, cancellation) {
610
+ let result;
611
+ try {
612
+ result = await this.getRestResource(provider, token, namespace, baseUrl, resource, version, cancellation);
613
+ } catch (ex) {
614
+ if (ex instanceof RequestNotFoundError) throw new Error("Azure DevOps REST resource discovery failed", { cause: ex });
615
+ throw ex;
616
+ }
617
+ if (result == null) throw new Error(`Azure DevOps REST resource ${resource} is unavailable`);
618
+ return result;
619
+ }
539
620
  async request(provider, token, baseUrl, route, options, scope, cancellation) {
540
621
  const { accessToken } = token;
541
622
  const url = baseUrl ? `${baseUrl}/${route}` : route;
@@ -670,8 +751,8 @@ __decorate([trace({ args: (provider, token, collection) => ({
670
751
  collection
671
752
  }) })], AzureDevOpsApi.prototype, "countWorkItems", null);
672
753
  /** Wires an {@link AzureDevOpsApi} from the full runtime context, mapping `ctx` down to the narrow config. */
673
- function createAzureDevOpsApi(ctx, workItemStates) {
674
- return new AzureDevOpsApi(baseProviderApiConfig(ctx), workItemStates);
754
+ function createAzureDevOpsApi(ctx, workItemStates, workItemDescriptions) {
755
+ return new AzureDevOpsApi(baseProviderApiConfig(ctx), workItemStates, workItemDescriptions);
675
756
  }
676
757
  //#endregion
677
758
  export { AzureDevOpsApi, createAzureDevOpsApi };