@gitkraken/core-gitlens 0.10.6 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/dist/git/cache.d.ts +34 -2
  3. package/dist/git/cache.js +53 -7
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/context.d.ts +1 -1
  6. package/dist/git/models/repository.d.ts +3 -2
  7. package/dist/git/models/repository.js +5 -4
  8. package/dist/git/models/repository.js.map +1 -1
  9. package/dist/git/providers/branches.d.ts +22 -0
  10. package/dist/git/providers/config.d.ts +3 -1
  11. package/dist/git/providers/diff.d.ts +1 -0
  12. package/dist/git/providers/provider.d.ts +13 -1
  13. package/dist/git/providers/staging.d.ts +1 -1
  14. package/dist/git/service.d.ts +17 -1
  15. package/dist/git/service.js +59 -8
  16. package/dist/git/service.js.map +1 -1
  17. package/dist/git/watching/changeEvent.d.ts +48 -5
  18. package/dist/git/watching/changeEvent.js +82 -7
  19. package/dist/git/watching/changeEvent.js.map +1 -1
  20. package/dist/git/watching/classifyChange.d.ts +13 -2
  21. package/dist/git/watching/classifyChange.js +40 -5
  22. package/dist/git/watching/classifyChange.js.map +1 -1
  23. package/dist/git/watching/gitIgnoreFilter.d.ts +57 -10
  24. package/dist/git/watching/gitIgnoreFilter.js +186 -29
  25. package/dist/git/watching/gitIgnoreFilter.js.map +1 -1
  26. package/dist/git/watching/initWatcher.d.ts +5 -1
  27. package/dist/git/watching/initWatcher.js +1 -1
  28. package/dist/git/watching/initWatcher.js.map +1 -1
  29. package/dist/git/watching/provider.d.ts +33 -8
  30. package/dist/git/watching/watchGroup.d.ts +13 -2
  31. package/dist/git/watching/watchGroup.js +60 -14
  32. package/dist/git/watching/watchGroup.js.map +1 -1
  33. package/dist/git/watching/watchService.d.ts +42 -1
  34. package/dist/git/watching/watchService.js +172 -20
  35. package/dist/git/watching/watchService.js.map +1 -1
  36. package/dist/git/watching/watchSession.d.ts +50 -17
  37. package/dist/git/watching/watchSession.js +274 -83
  38. package/dist/git/watching/watchSession.js.map +1 -1
  39. package/dist/git/watching/watcherPatterns.d.ts +13 -2
  40. package/dist/git/watching/watcherPatterns.js +22 -2
  41. package/dist/git/watching/watcherPatterns.js.map +1 -1
  42. package/dist/git/watching/worktreeSummary.d.ts +121 -0
  43. package/dist/git/watching/worktreeSummary.js +427 -0
  44. package/dist/git/watching/worktreeSummary.js.map +1 -0
  45. package/dist/git-cli/cliGitProvider.d.ts +6 -1
  46. package/dist/git-cli/cliGitProvider.js +87 -2
  47. package/dist/git-cli/cliGitProvider.js.map +1 -1
  48. package/dist/git-cli/exec/git.js +1 -1
  49. package/dist/git-cli/providers/blame.js +1 -1
  50. package/dist/git-cli/providers/branches.d.ts +32 -1
  51. package/dist/git-cli/providers/branches.js +195 -0
  52. package/dist/git-cli/providers/branches.js.map +1 -1
  53. package/dist/git-cli/providers/commits.js +1 -1
  54. package/dist/git-cli/providers/config.d.ts +3 -0
  55. package/dist/git-cli/providers/config.js +14 -8
  56. package/dist/git-cli/providers/config.js.map +1 -1
  57. package/dist/git-cli/providers/contributors.js +1 -1
  58. package/dist/git-cli/providers/diff.d.ts +3 -0
  59. package/dist/git-cli/providers/diff.js +3 -2
  60. package/dist/git-cli/providers/diff.js.map +1 -1
  61. package/dist/git-cli/providers/graph.js +1 -1
  62. package/dist/git-cli/providers/maintenance.js +1 -1
  63. package/dist/git-cli/providers/pausedOperations.js +2 -2
  64. package/dist/git-cli/providers/pausedOperations.js.map +1 -1
  65. package/dist/git-cli/providers/refs.js +1 -1
  66. package/dist/git-cli/providers/staging.js +2 -2
  67. package/dist/git-cli/providers/status.js +1 -1
  68. package/dist/git-cli/service.js +1 -0
  69. package/dist/git-cli/service.js.map +1 -1
  70. package/dist/ipc/discovery.js +1 -1
  71. package/dist/ipc/ipcServer.js +1 -1
  72. package/dist/plus/agents/providers/claudeCodeTranscript.js +6 -6
  73. package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
  74. package/dist/plus/agents/providers/gkAgentProvider.js +2 -2
  75. package/dist/plus/ai/providers/openAICompatibleProviderBase.js +1 -1
  76. package/dist/plus/git-github/api/github.d.ts +1 -1
  77. package/dist/plus/git-github/api/github.js +2 -1
  78. package/dist/plus/git-github/api/github.js.map +1 -1
  79. package/dist/plus/git-github/providers/github/blame.js +1 -1
  80. package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js +1 -1
  81. package/dist/plus/integrations/context.d.ts +1 -1
  82. package/dist/plus/integrations/integrationService.js +1 -1
  83. package/dist/plus/integrations/models/integration.d.ts +1 -1
  84. package/dist/plus/integrations/models/integration.js +2 -2
  85. package/dist/plus/integrations/models/integration.js.map +1 -1
  86. package/dist/plus/integrations/providers/azure/azure.js +1 -1
  87. package/dist/plus/integrations/providers/bitbucket-server.d.ts +1 -1
  88. package/dist/plus/integrations/providers/jira.d.ts +2 -0
  89. package/dist/plus/integrations/providers/jira.js +5 -0
  90. package/dist/plus/integrations/providers/jira.js.map +1 -1
  91. package/dist/plus/integrations/providers/linear.d.ts +7 -0
  92. package/dist/plus/integrations/providers/linear.js +19 -0
  93. package/dist/plus/integrations/providers/linear.js.map +1 -1
  94. package/dist/plus/integrations/providers/trello.d.ts +8 -0
  95. package/dist/plus/integrations/providers/trello.js +22 -0
  96. package/dist/plus/integrations/providers/trello.js.map +1 -1
  97. package/dist/plus/integrations/reads/currentAccount.d.ts +1 -1
  98. package/dist/plus/integrations/reads/currentAccount.js +1 -1
  99. package/dist/plus/integrations/reads/currentAccount.js.map +1 -1
  100. package/dist/plus/integrations/reads/issueTracker.js +1 -0
  101. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  102. package/dist/utils/env/node/exec.js +2 -2
  103. package/dist/utils/event.d.ts +1 -1
  104. package/docs/integrations.md +6 -6
  105. package/docs/kepler-read-api-parity.md +2 -2
  106. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"trello.js","names":[],"sources":["../../../../../plus/integrations/src/providers/trello.ts"],"sourcesContent":["import type { Account } from '@gitlens/git/models/author.js';\nimport type { Issue, IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequest, IssueOrPullRequestType } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type { IssueResourceDescriptor, ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { isIssueResourceDescriptor } from '@gitlens/git/utils/resourceDescriptor.utils.js';\nimport type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.js';\nimport type { ProviderAuthenticationSession } from '../authentication/models.js';\nimport { toTokenWithInfo } from '../authentication/models.js';\nimport { IssuesCloudHostIntegrationId } from '../constants.js';\nimport { IntegrationReadUnavailableError } from '../errors.js';\nimport type { IssuesForProjectOptions, ProjectIssuesDrain } from '../models/issueReads.js';\nimport { IssuesIntegration } from '../models/issuesIntegration.js';\nimport { fromProviderIssue, providersMetadata, toIssueShape } from './models.js';\n\nconst metadata = providersMetadata[IssuesCloudHostIntegrationId.Trello];\nconst authProvider = Object.freeze({ id: metadata.id, scopes: metadata.scopes });\n\n/** A Trello board, surfaced as both a \"resource\" (org analogue) and a \"project\" for issue reads. */\nexport interface TrelloBoardDescriptor extends IssueResourceDescriptor {}\n\nexport class TrelloIntegration extends IssuesIntegration<IssuesCloudHostIntegrationId.Trello> {\n\treadonly authProvider: IntegrationAuthenticationProviderDescriptor = authProvider;\n\n\toverride get id(): IssuesCloudHostIntegrationId.Trello {\n\t\treturn IssuesCloudHostIntegrationId.Trello;\n\t}\n\tprotected override get key(): 'trello' {\n\t\treturn 'trello';\n\t}\n\toverride get name(): string {\n\t\treturn metadata.name;\n\t}\n\toverride get domain(): string {\n\t\treturn metadata.domain;\n\t}\n\n\t/** The Trello client requires the app key paired with the token; a session without one can't read. */\n\tprivate appKeyFor(session: ProviderAuthenticationSession): string | undefined {\n\t\treturn session.appKey;\n\t}\n\n\t/**\n\t * Resolves the app key or throws — a session that authenticated but carries no app key can't read Trello,\n\t * and must be surfaced as a broken read (warning + fetchFailed) rather than an empty account.\n\t */\n\tprivate requireAppKey(session: ProviderAuthenticationSession): string {\n\t\tconst appKey = this.appKeyFor(session);\n\t\tif (appKey == null) {\n\t\t\tthrow new IntegrationReadUnavailableError(metadata.name, 'missing app key (session has no appKey)');\n\t\t}\n\t\treturn appKey;\n\t}\n\n\tprotected override async getProviderAccountForResource(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: ResourceDescriptor,\n\t): Promise<Account | undefined> {\n\t\tconst appKey = this.requireAppKey(session);\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst user = await api.getTrelloCurrentUser(toTokenWithInfo(this.id, session), appKey);\n\t\tif (user == null) return undefined;\n\n\t\treturn {\n\t\t\tprovider: this,\n\t\t\tid: user.id,\n\t\t\tname: user.name,\n\t\t\tusername: user.username,\n\t\t\temail: user.email,\n\t\t\tavatarUrl: user.avatarUrl ?? undefined,\n\t\t};\n\t}\n\n\tprotected override async getProviderResourcesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<TrelloBoardDescriptor[] | undefined> {\n\t\tconst appKey = this.requireAppKey(session);\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst boards = await api.getTrelloBoardsForCurrentUser(toTokenWithInfo(this.id, session), appKey);\n\t\treturn boards?.map(b => ({ key: b.id, id: b.id, name: b.name }));\n\t}\n\n\tprotected override getProviderProjectsForResources(\n\t\t_session: ProviderAuthenticationSession,\n\t\tresources: ResourceDescriptor[],\n\t): Promise<TrelloBoardDescriptor[] | undefined> {\n\t\t// Trello boards are both the resource and the project scope, so projects are the boards themselves.\n\t\treturn Promise.resolve(\n\t\t\tresources.filter(isIssueResourceDescriptor).map(r => ({ key: r.key, id: r.id, name: r.name })),\n\t\t);\n\t}\n\n\tprotected override async getProviderIssuesForProject(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: ResourceDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.getProviderIssuesForProjectWithTruncation(session, project, options))?.values;\n\t}\n\n\tprotected override async getProviderIssuesForProjectWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: ResourceDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<ProjectIssuesDrain | undefined> {\n\t\t// A non-issue descriptor genuinely has nothing to read (empty), but a missing app key is a broken read.\n\t\tif (!isIssueResourceDescriptor(project)) return undefined;\n\n\t\t// The board read always excludes archived cards, and a card has no other state, so only open cards can be\n\t\t// read. Refused rather than ignored: answering `closed` with the open cards would publish them as closed.\n\t\tif (options?.state != null && options.state !== 'open') {\n\t\t\tthrow new IntegrationReadUnavailableError(metadata.name, `issue state '${options.state}' is not supported`);\n\t\t}\n\n\t\tconst appKey = this.requireAppKey(session);\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\n\t\t// Enrich issues with their board list names (card status), mirroring gitkraken.dev's Trello reads.\n\t\tconst lists = await api.getTrelloListsForBoard(tokenWithInfo, appKey, project.id);\n\t\tconst trelloBoardListsById = lists?.reduce<Record<string, { name: string }>>((acc, list) => {\n\t\t\tacc[list.id] = { name: list.name };\n\t\t\treturn acc;\n\t\t}, {});\n\t\tconst boardProject = {\n\t\t\tid: project.id,\n\t\t\tname: project.name,\n\t\t\tresourceId: project.id,\n\t\t\tresourceName: project.name,\n\t\t};\n\n\t\tconst result = await api.getTrelloIssuesForBoard(tokenWithInfo, appKey, project.id, {\n\t\t\tassigneeLogins: options?.user != null ? [options.user] : undefined,\n\t\t\ttrelloBoardListsById: trelloBoardListsById,\n\t\t\tsort: options?.sort,\n\t\t});\n\n\t\tconst values = result.values.flatMap(issue => {\n\t\t\tconst mapped = toIssueShape(issue, this, { projection: 'project' });\n\t\t\treturn mapped == null ? [] : [{ ...mapped, project: boardProject } satisfies IssueShape];\n\t\t});\n\n\t\t// Trello's search caps results and reports the cap through `metadata.completeness` (partial/unknown),\n\t\t// never a cursor. Surface that as terminal truncation; there is no next page to fetch, so retrying the\n\t\t// same read cannot recover the omitted cards (D11).\n\t\tconst truncated = result.metadata != null && result.metadata.completeness !== 'complete';\n\t\treturn truncated\n\t\t\t? { values: values, truncated: true, recovery: 'none', metadata: result.metadata }\n\t\t\t: { values: values, truncated: false, metadata: result.metadata };\n\t}\n\n\tprotected override searchProviderMyIssues(\n\t\t_session: ProviderAuthenticationSession,\n\t\t_resources?: ResourceDescriptor[],\n\t\t_cancellation?: AbortSignal,\n\t): Promise<IssueShape[] | undefined> {\n\t\t// Trello has no cross-board \"issues assigned to me\" endpoint; issues are read per board via\n\t\t// getIssuesForProject. Callers that need them enumerate boards first.\n\t\treturn Promise.resolve(undefined);\n\t}\n\n\tprotected override getProviderLinkedIssueOrPullRequest(\n\t\t_session: ProviderAuthenticationSession,\n\t\t_resource: ResourceDescriptor,\n\t\t_id: { id: string; key: string },\n\t\t_type: undefined | IssueOrPullRequestType,\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\treturn Promise.resolve(undefined);\n\t}\n\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: ResourceDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\tif (!isIssueResourceDescriptor(resource)) return undefined;\n\n\t\tconst appKey = this.requireAppKey(session);\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst lists = await api.getTrelloListsForBoard(tokenWithInfo, appKey, resource.id);\n\t\tconst trelloBoardListsById = lists?.reduce<Record<string, { name: string }>>((acc, list) => {\n\t\t\tacc[list.id] = { name: list.name };\n\t\t\treturn acc;\n\t\t}, {});\n\n\t\t// Branch-association round-trips use the encoded entity identifier's stable Trello card id. Resolve that\n\t\t// directly so large boards don't require a capped whole-board scan; keep a numeric idShort fallback so\n\t\t// legacy/manual callers that still pass the board-local display number continue to resolve.\n\t\tlet issue = await api.getTrelloCard(tokenWithInfo, appKey, id, {\n\t\t\ttrelloBoardListsById: trelloBoardListsById,\n\t\t});\n\t\tif (issue == null && /^[0-9]+$/.test(id)) {\n\t\t\tissue = (\n\t\t\t\tawait api.getTrelloIssuesForBoard(tokenWithInfo, appKey, resource.id, {\n\t\t\t\t\ttrelloBoardListsById: trelloBoardListsById,\n\t\t\t\t})\n\t\t\t).values.find(issue => issue.number === id);\n\t\t}\n\n\t\treturn issue != null\n\t\t\t? fromProviderIssue(issue, this, {\n\t\t\t\t\tprojection: 'point',\n\t\t\t\t\tproject: {\n\t\t\t\t\t\tid: resource.id,\n\t\t\t\t\t\tname: resource.name,\n\t\t\t\t\t\tresourceId: resource.id,\n\t\t\t\t\t\tresourceName: resource.name,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t: undefined;\n\t}\n}\n"],"mappings":";;;;;;;AAcA,MAAM,WAAW,kBAAA;AACjB,MAAM,eAAe,OAAO,OAAO;CAAE,IAAI,SAAS;CAAI,QAAQ,SAAS;AAAO,CAAC;AAK/E,IAAa,oBAAb,cAAuC,kBAAuD;CAC7F,eAAqE;CAErE,IAAa,KAA0C;EACtD,OAAA;CACD;CACA,IAAuB,MAAgB;EACtC,OAAO;CACR;CACA,IAAa,OAAe;EAC3B,OAAO,SAAS;CACjB;CACA,IAAa,SAAiB;EAC7B,OAAO,SAAS;CACjB;;CAGA,UAAkB,SAA4D;EAC7E,OAAO,QAAQ;CAChB;;;;;CAMA,cAAsB,SAAgD;EACrE,MAAM,SAAS,KAAK,UAAU,OAAO;EACrC,IAAI,UAAU,MACb,MAAM,IAAI,gCAAgC,SAAS,MAAM,yCAAyC;EAEnG,OAAO;CACR;CAEA,MAAyB,8BACxB,SACA,WAC+B;EAC/B,MAAM,SAAS,KAAK,cAAc,OAAO;EAGzC,MAAM,OAAO,OAAM,MADD,KAAK,gBAAgB,EAAA,CAChB,qBAAqB,gBAAgB,KAAK,IAAI,OAAO,GAAG,MAAM;EACrF,IAAI,QAAQ,MAAM,OAAO,KAAA;EAEzB,OAAO;GACN,UAAU;GACV,IAAI,KAAK;GACT,MAAM,KAAK;GACX,UAAU,KAAK;GACf,OAAO,KAAK;GACZ,WAAW,KAAK,aAAa,KAAA;EAC9B;CACD;CAEA,MAAyB,4BACxB,SAC+C;EAC/C,MAAM,SAAS,KAAK,cAAc,OAAO;EAIzC,QAAO,OADc,MADH,KAAK,gBAAgB,EAAA,CACd,8BAA8B,gBAAgB,KAAK,IAAI,OAAO,GAAG,MAAM,EAAA,EACjF,KAAI,OAAM;GAAE,KAAK,EAAE;GAAI,IAAI,EAAE;GAAI,MAAM,EAAE;EAAK,EAAE;CAChE;CAEA,gCACC,UACA,WAC+C;EAE/C,OAAO,QAAQ,QACd,UAAU,OAAO,yBAAyB,CAAC,CAAC,KAAI,OAAM;GAAE,KAAK,EAAE;GAAK,IAAI,EAAE;GAAI,MAAM,EAAE;EAAK,EAAE,CAC9F;CACD;CAEA,MAAyB,4BACxB,SACA,SACA,SACoC;EACpC,QAAQ,MAAM,KAAK,0CAA0C,SAAS,SAAS,OAAO,EAAA,EAAI;CAC3F;CAEA,MAAyB,0CACxB,SACA,SACA,SAC0C;EAE1C,IAAI,CAAC,0BAA0B,OAAO,GAAG,OAAO,KAAA;EAIhD,IAAI,SAAS,SAAS,QAAQ,QAAQ,UAAU,QAC/C,MAAM,IAAI,gCAAgC,SAAS,MAAM,gBAAgB,QAAQ,MAAM,mBAAmB;EAG3G,MAAM,SAAS,KAAK,cAAc,OAAO;EAEzC,MAAM,MAAM,MAAM,KAAK,gBAAgB;EACvC,MAAM,gBAAgB,gBAAgB,KAAK,IAAI,OAAO;EAItD,MAAM,wBAAuB,MADT,IAAI,uBAAuB,eAAe,QAAQ,QAAQ,EAAE,EAAA,EAC5C,QAA0C,KAAK,SAAS;GAC3F,IAAI,KAAK,MAAM,EAAE,MAAM,KAAK,KAAK;GACjC,OAAO;EACR,GAAG,CAAC,CAAC;EACL,MAAM,eAAe;GACpB,IAAI,QAAQ;GACZ,MAAM,QAAQ;GACd,YAAY,QAAQ;GACpB,cAAc,QAAQ;EACvB;EAEA,MAAM,SAAS,MAAM,IAAI,wBAAwB,eAAe,QAAQ,QAAQ,IAAI;GACnF,gBAAgB,SAAS,QAAQ,OAAO,CAAC,QAAQ,IAAI,IAAI,KAAA;GACnC;GACtB,MAAM,SAAS;EAChB,CAAC;EAED,MAAM,SAAS,OAAO,OAAO,SAAQ,UAAS;GAC7C,MAAM,SAAS,aAAa,OAAO,MAAM,EAAE,YAAY,UAAU,CAAC;GAClE,OAAO,UAAU,OAAO,CAAC,IAAI,CAAC;IAAE,GAAG;IAAQ,SAAS;GAAa,CAAsB;EACxF,CAAC;EAMD,OADkB,OAAO,YAAY,QAAQ,OAAO,SAAS,iBAAiB,aAE3E;GAAU;GAAQ,WAAW;GAAM,UAAU;GAAQ,UAAU,OAAO;EAAS,IAC/E;GAAU;GAAQ,WAAW;GAAO,UAAU,OAAO;EAAS;CAClE;CAEA,uBACC,UACA,YACA,eACoC;EAGpC,OAAO,QAAQ,QAAQ,KAAA,CAAS;CACjC;CAEA,oCACC,UACA,WACA,KACA,OAC0C;EAC1C,OAAO,QAAQ,QAAQ,KAAA,CAAS;CACjC;CAEA,MAAyB,iBACxB,SACA,UACA,IAC6B;EAC7B,IAAI,CAAC,0BAA0B,QAAQ,GAAG,OAAO,KAAA;EAEjD,MAAM,SAAS,KAAK,cAAc,OAAO;EACzC,MAAM,MAAM,MAAM,KAAK,gBAAgB;EACvC,MAAM,gBAAgB,gBAAgB,KAAK,IAAI,OAAO;EAEtD,MAAM,wBAAuB,MADT,IAAI,uBAAuB,eAAe,QAAQ,SAAS,EAAE,EAAA,EAC7C,QAA0C,KAAK,SAAS;GAC3F,IAAI,KAAK,MAAM,EAAE,MAAM,KAAK,KAAK;GACjC,OAAO;EACR,GAAG,CAAC,CAAC;EAKL,IAAI,QAAQ,MAAM,IAAI,cAAc,eAAe,QAAQ,IAAI,EACxC,qBACvB,CAAC;EACD,IAAI,SAAS,QAAQ,WAAW,KAAK,EAAE,GACtC,SACC,MAAM,IAAI,wBAAwB,eAAe,QAAQ,SAAS,IAAI,EAC/C,qBACvB,CAAC,EAAA,CACA,OAAO,MAAK,UAAS,MAAM,WAAW,EAAE;EAG3C,OAAO,SAAS,OACb,kBAAkB,OAAO,MAAM;GAC/B,YAAY;GACZ,SAAS;IACR,IAAI,SAAS;IACb,MAAM,SAAS;IACf,YAAY,SAAS;IACrB,cAAc,SAAS;GACxB;EACD,CAAC,IACA,KAAA;CACJ;AACD"}
1
+ {"version":3,"file":"trello.js","names":[],"sources":["../../../../../plus/integrations/src/providers/trello.ts"],"sourcesContent":["import type { Account } from '@gitlens/git/models/author.js';\nimport type { Issue, IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequest, IssueOrPullRequestType } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type { IssueResourceDescriptor, ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { isIssueResourceDescriptor } from '@gitlens/git/utils/resourceDescriptor.utils.js';\nimport { PromiseCache } from '@gitlens/utils/promiseCache.js';\nimport type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.js';\nimport type { ProviderAuthenticationSession } from '../authentication/models.js';\nimport { toTokenWithInfo } from '../authentication/models.js';\nimport { IssuesCloudHostIntegrationId } from '../constants.js';\nimport { IntegrationReadUnavailableError } from '../errors.js';\nimport type { IssuesForProjectOptions, ProjectIssuesDrain } from '../models/issueReads.js';\nimport { IssuesIntegration } from '../models/issuesIntegration.js';\nimport { fromProviderIssue, providersMetadata, toIssueShape } from './models.js';\n\nconst metadata = providersMetadata[IssuesCloudHostIntegrationId.Trello];\nconst authProvider = Object.freeze({ id: metadata.id, scopes: metadata.scopes });\n/** Long enough to span one tracker read, short enough that a profile change shows on the next. */\nconst recentAccountTTL = 60 * 1000;\n\n/** A Trello board, surfaced as both a \"resource\" (org analogue) and a \"project\" for issue reads. */\nexport interface TrelloBoardDescriptor extends IssueResourceDescriptor {}\n\nexport class TrelloIntegration extends IssuesIntegration<IssuesCloudHostIntegrationId.Trello> {\n\treadonly authProvider: IntegrationAuthenticationProviderDescriptor = authProvider;\n\n\toverride get id(): IssuesCloudHostIntegrationId.Trello {\n\t\treturn IssuesCloudHostIntegrationId.Trello;\n\t}\n\tprotected override get key(): 'trello' {\n\t\treturn 'trello';\n\t}\n\toverride get name(): string {\n\t\treturn metadata.name;\n\t}\n\toverride get domain(): string {\n\t\treturn metadata.domain;\n\t}\n\n\t/** The Trello client requires the app key paired with the token; a session without one can't read. */\n\tprivate appKeyFor(session: ProviderAuthenticationSession): string | undefined {\n\t\treturn session.appKey;\n\t}\n\n\t/**\n\t * Resolves the app key or throws — a session that authenticated but carries no app key can't read Trello,\n\t * and must be surfaced as a broken read (warning + fetchFailed) rather than an empty account.\n\t */\n\tprivate requireAppKey(session: ProviderAuthenticationSession): string {\n\t\tconst appKey = this.appKeyFor(session);\n\t\tif (appKey == null) {\n\t\t\tthrow new IntegrationReadUnavailableError(metadata.name, 'missing app key (session has no appKey)');\n\t\t}\n\t\treturn appKey;\n\t}\n\n\t/**\n\t * Always asks Trello: the host's current-account cache decides when this runs. The member lookup is\n\t * account-wide, so the answer is lent briefly to the per-board lookup a tracker read makes right after it.\n\t */\n\tprotected override async getProviderCurrentAccount(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<Account | undefined> {\n\t\tconst account = await this.fetchAccount(session);\n\t\tif (account != null) {\n\t\t\tthis._recentAccounts.set(session.accessToken, Promise.resolve(account));\n\t\t}\n\t\treturn account;\n\t}\n\n\tprotected override async getProviderAccountForResource(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: ResourceDescriptor,\n\t): Promise<Account | undefined> {\n\t\tconst appKey = this.requireAppKey(session);\n\t\treturn this._recentAccounts.getOrCreate(session.accessToken, () => this.fetchAccount(session, appKey), {\n\t\t\tevictWhen: account => account == null,\n\t\t});\n\t}\n\n\toverride invalidateDiscoveryCaches(): void {\n\t\tthis._recentAccounts.clear();\n\t}\n\n\tprivate readonly _recentAccounts = new PromiseCache<string, Account | undefined>({\n\t\tcapacity: 10,\n\t\tcreateTTL: recentAccountTTL,\n\t});\n\n\tprivate async fetchAccount(\n\t\tsession: ProviderAuthenticationSession,\n\t\tappKey: string = this.requireAppKey(session),\n\t): Promise<Account | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst user = await api.getTrelloCurrentUser(toTokenWithInfo(this.id, session), appKey);\n\t\tif (user == null) return undefined;\n\n\t\treturn {\n\t\t\tprovider: this,\n\t\t\tid: user.id,\n\t\t\tname: user.name,\n\t\t\tusername: user.username,\n\t\t\temail: user.email,\n\t\t\tavatarUrl: user.avatarUrl ?? undefined,\n\t\t};\n\t}\n\n\tprotected override async getProviderResourcesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<TrelloBoardDescriptor[] | undefined> {\n\t\tconst appKey = this.requireAppKey(session);\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst boards = await api.getTrelloBoardsForCurrentUser(toTokenWithInfo(this.id, session), appKey);\n\t\treturn boards?.map(b => ({ key: b.id, id: b.id, name: b.name }));\n\t}\n\n\tprotected override getProviderProjectsForResources(\n\t\t_session: ProviderAuthenticationSession,\n\t\tresources: ResourceDescriptor[],\n\t): Promise<TrelloBoardDescriptor[] | undefined> {\n\t\t// Trello boards are both the resource and the project scope, so projects are the boards themselves.\n\t\treturn Promise.resolve(\n\t\t\tresources.filter(isIssueResourceDescriptor).map(r => ({ key: r.key, id: r.id, name: r.name })),\n\t\t);\n\t}\n\n\tprotected override async getProviderIssuesForProject(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: ResourceDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.getProviderIssuesForProjectWithTruncation(session, project, options))?.values;\n\t}\n\n\tprotected override async getProviderIssuesForProjectWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: ResourceDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<ProjectIssuesDrain | undefined> {\n\t\t// A non-issue descriptor genuinely has nothing to read (empty), but a missing app key is a broken read.\n\t\tif (!isIssueResourceDescriptor(project)) return undefined;\n\n\t\t// The board read always excludes archived cards, and a card has no other state, so only open cards can be\n\t\t// read. Refused rather than ignored: answering `closed` with the open cards would publish them as closed.\n\t\tif (options?.state != null && options.state !== 'open') {\n\t\t\tthrow new IntegrationReadUnavailableError(metadata.name, `issue state '${options.state}' is not supported`);\n\t\t}\n\n\t\tconst appKey = this.requireAppKey(session);\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\n\t\t// Enrich issues with their board list names (card status), mirroring gitkraken.dev's Trello reads.\n\t\tconst lists = await api.getTrelloListsForBoard(tokenWithInfo, appKey, project.id);\n\t\tconst trelloBoardListsById = lists?.reduce<Record<string, { name: string }>>((acc, list) => {\n\t\t\tacc[list.id] = { name: list.name };\n\t\t\treturn acc;\n\t\t}, {});\n\t\tconst boardProject = {\n\t\t\tid: project.id,\n\t\t\tname: project.name,\n\t\t\tresourceId: project.id,\n\t\t\tresourceName: project.name,\n\t\t};\n\n\t\tconst result = await api.getTrelloIssuesForBoard(tokenWithInfo, appKey, project.id, {\n\t\t\tassigneeLogins: options?.user != null ? [options.user] : undefined,\n\t\t\ttrelloBoardListsById: trelloBoardListsById,\n\t\t\tsort: options?.sort,\n\t\t});\n\n\t\tconst values = result.values.flatMap(issue => {\n\t\t\tconst mapped = toIssueShape(issue, this, { projection: 'project' });\n\t\t\treturn mapped == null ? [] : [{ ...mapped, project: boardProject } satisfies IssueShape];\n\t\t});\n\n\t\t// Trello's search caps results and reports the cap through `metadata.completeness` (partial/unknown),\n\t\t// never a cursor. Surface that as terminal truncation; there is no next page to fetch, so retrying the\n\t\t// same read cannot recover the omitted cards (D11).\n\t\tconst truncated = result.metadata != null && result.metadata.completeness !== 'complete';\n\t\treturn truncated\n\t\t\t? { values: values, truncated: true, recovery: 'none', metadata: result.metadata }\n\t\t\t: { values: values, truncated: false, metadata: result.metadata };\n\t}\n\n\tprotected override searchProviderMyIssues(\n\t\t_session: ProviderAuthenticationSession,\n\t\t_resources?: ResourceDescriptor[],\n\t\t_cancellation?: AbortSignal,\n\t): Promise<IssueShape[] | undefined> {\n\t\t// Trello has no cross-board \"issues assigned to me\" endpoint; issues are read per board via\n\t\t// getIssuesForProject. Callers that need them enumerate boards first.\n\t\treturn Promise.resolve(undefined);\n\t}\n\n\tprotected override getProviderLinkedIssueOrPullRequest(\n\t\t_session: ProviderAuthenticationSession,\n\t\t_resource: ResourceDescriptor,\n\t\t_id: { id: string; key: string },\n\t\t_type: undefined | IssueOrPullRequestType,\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\treturn Promise.resolve(undefined);\n\t}\n\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: ResourceDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\tif (!isIssueResourceDescriptor(resource)) return undefined;\n\n\t\tconst appKey = this.requireAppKey(session);\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst lists = await api.getTrelloListsForBoard(tokenWithInfo, appKey, resource.id);\n\t\tconst trelloBoardListsById = lists?.reduce<Record<string, { name: string }>>((acc, list) => {\n\t\t\tacc[list.id] = { name: list.name };\n\t\t\treturn acc;\n\t\t}, {});\n\n\t\t// Branch-association round-trips use the encoded entity identifier's stable Trello card id. Resolve that\n\t\t// directly so large boards don't require a capped whole-board scan; keep a numeric idShort fallback so\n\t\t// legacy/manual callers that still pass the board-local display number continue to resolve.\n\t\tlet issue = await api.getTrelloCard(tokenWithInfo, appKey, id, {\n\t\t\ttrelloBoardListsById: trelloBoardListsById,\n\t\t});\n\t\tif (issue == null && /^[0-9]+$/.test(id)) {\n\t\t\tissue = (\n\t\t\t\tawait api.getTrelloIssuesForBoard(tokenWithInfo, appKey, resource.id, {\n\t\t\t\t\ttrelloBoardListsById: trelloBoardListsById,\n\t\t\t\t})\n\t\t\t).values.find(issue => issue.number === id);\n\t\t}\n\n\t\treturn issue != null\n\t\t\t? fromProviderIssue(issue, this, {\n\t\t\t\t\tprojection: 'point',\n\t\t\t\t\tproject: {\n\t\t\t\t\t\tid: resource.id,\n\t\t\t\t\t\tname: resource.name,\n\t\t\t\t\t\tresourceId: resource.id,\n\t\t\t\t\t\tresourceName: resource.name,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t: undefined;\n\t}\n}\n"],"mappings":"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"}
@@ -20,7 +20,7 @@ export interface CurrentAccountResult {
20
20
  fetchFailed?: boolean;
21
21
  }
22
22
  /**
23
- * Resolves the viewer's own account for a git host connection. Issue trackers refuse: they only have a per-resource account.
23
+ * Resolves the viewer's own account for a connection. Jira Data Center refuses: it has no account-wide lookup.
24
24
  *
25
25
  * Goes THROUGH `Integration.getCurrentAccount`'s own cache (the hook a host supplies as
26
26
  * `IntegrationManagerCacheProvider.getCurrentAccount`) rather than adding a second one, so this read and
@@ -3,7 +3,7 @@ import { noConnectionWarning, unsupportedWarning } from "./warnings.js";
3
3
  import { runCaptured } from "./drains.js";
4
4
  //#region ../plus/integrations/src/reads/currentAccount.ts
5
5
  /**
6
- * Resolves the viewer's own account for a git host connection. Issue trackers refuse: they only have a per-resource account.
6
+ * Resolves the viewer's own account for a connection. Jira Data Center refuses: it has no account-wide lookup.
7
7
  *
8
8
  * Goes THROUGH `Integration.getCurrentAccount`'s own cache (the hook a host supplies as
9
9
  * `IntegrationManagerCacheProvider.getCurrentAccount`) rather than adding a second one, so this read and
@@ -1 +1 @@
1
- {"version":3,"file":"currentAccount.js","names":[],"sources":["../../../../../plus/integrations/src/reads/currentAccount.ts"],"sourcesContent":["import type { Account } from '@gitlens/git/models/author.js';\nimport type { IntegrationIds } from '../constants.js';\nimport type { ProviderWarning } from '../results.js';\nimport { appendDedupedWarning } from '../results.js';\nimport type { ProviderReadContext } from './context.js';\nimport { runCaptured } from './drains.js';\nimport { noConnectionWarning, unsupportedWarning } from './warnings.js';\n\n/**\n * \"Who am I on this provider / connection\" — {@link IntegrationBase.getCurrentAccount}'s own answer, exposed on\n * the manager so a caller can ask for it directly instead of waiting for some other read to warm it as a side\n * effect.\n */\nexport interface CurrentAccountResult {\n\t/** The signed-in account, when the provider answered. */\n\taccount?: Account;\n\twarnings: ProviderWarning[];\n\t/**\n\t * Set whenever `account` is absent. Unlike a list/batch read, this one has no benign empty answer — \"who am\n\t * I\" is either resolved or it couldn't be told, never a legitimately empty result — so `account` is never\n\t * missing without a warning explaining why.\n\t */\n\tfetchFailed?: boolean;\n}\n\n/**\n * Resolves the viewer's own account for a git host connection. Issue trackers refuse: they only have a per-resource account.\n *\n * Goes THROUGH `Integration.getCurrentAccount`'s own cache (the hook a host supplies as\n * `IntegrationManagerCacheProvider.getCurrentAccount`) rather than adding a second one, so this read and\n * whatever else warms that cache — today, authorship enrichment on the pull request reads — stay one cache.\n */\nexport async function getCurrentAccount(\n\tctx: ProviderReadContext,\n\toptions: { providerId: IntegrationIds; connectionId?: string; domain?: string },\n): Promise<CurrentAccountResult> {\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\tif (early.warnings.length > 0) return { warnings: early.warnings, fetchFailed: true };\n\n\t\t// The untargeted primary path reads a disconnected provider as a silent empty result on the list reads; an\n\t\t// absent account must still carry a warning here, as on the batch reads — see `CurrentAccountResult.fetchFailed`.\n\t\tconst domain = ctx.resolveDomainForRead(options.providerId, options.connectionId, options.domain);\n\t\treturn { warnings: [noConnectionWarning(options.providerId, domain, options.connectionId)], fetchFailed: true };\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\n\tif (!integration.supportsCurrentAccount) {\n\t\treturn {\n\t\t\twarnings: [\n\t\t\t\tunsupportedWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t`Current account lookup is not supported by '${options.providerId}'.`,\n\t\t\t\t),\n\t\t\t],\n\t\t\tfetchFailed: true,\n\t\t};\n\t}\n\n\tconst { value: account, warning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\tasync () => {\n\t\t\tconst resolved = await integration.getCurrentAccount({ connectionId: options.connectionId });\n\t\t\t// `undefined` is returned bare (not `{ value: undefined }`) so `runCaptured` treats it the same as\n\t\t\t// a session it couldn't resolve — the only other way this read core answers nothing.\n\t\t\treturn resolved != null ? { value: resolved } : undefined;\n\t\t},\n\t\t{ warnOnMissingSession: true },\n\t);\n\n\tconst warnings: ProviderWarning[] = [];\n\tif (warning != null) {\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\n\treturn {\n\t\t...(account != null ? { account: account } : {}),\n\t\twarnings: warnings,\n\t\tfetchFailed: account == null ? true : undefined,\n\t};\n}\n"],"mappings":";;;;;;;;;;;AAgCA,eAAsB,kBACrB,KACA,SACgC;CAChC,MAAM,cAAc,MAAM,IAAI,sBAAsB,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAC5G,IAAI,eAAe,MAAM;EACxB,MAAM,QAAQ,IAAI,8BAA8B,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;EACxG,IAAI,MAAM,SAAS,SAAS,GAAG,OAAO;GAAE,UAAU,MAAM;GAAU,aAAa;EAAK;EAIpF,MAAM,SAAS,IAAI,qBAAqB,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;EAChG,OAAO;GAAE,UAAU,CAAC,oBAAoB,QAAQ,YAAY,QAAQ,QAAQ,YAAY,CAAC;GAAG,aAAa;EAAK;CAC/G;CAEA,MAAM,SAAS,IAAI,cAAc,aAAa,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAEtG,IAAI,CAAC,YAAY,wBAChB,OAAO;EACN,UAAU,CACT,mBACC,QAAQ,YACR,QACA,QAAQ,cACR,+CAA+C,QAAQ,WAAW,GACnE,CACD;EACA,aAAa;CACd;CAGD,MAAM,EAAE,OAAO,SAAS,YAAY,MAAM,YACzC,QAAQ,YACR,QACA,QAAQ,cACR,YAAY;EACX,MAAM,WAAW,MAAM,YAAY,kBAAkB,EAAE,cAAc,QAAQ,aAAa,CAAC;EAG3F,OAAO,YAAY,OAAO,EAAE,OAAO,SAAS,IAAI,KAAA;CACjD,GACA,EAAE,sBAAsB,KAAK,CAC9B;CAEA,MAAM,WAA8B,CAAC;CACrC,IAAI,WAAW,MACd,qBAAqB,UAAU,OAAO;CAGvC,OAAO;EACN,GAAI,WAAW,OAAO,EAAW,QAAQ,IAAI,CAAC;EACpC;EACV,aAAa,WAAW,OAAO,OAAO,KAAA;CACvC;AACD"}
1
+ {"version":3,"file":"currentAccount.js","names":[],"sources":["../../../../../plus/integrations/src/reads/currentAccount.ts"],"sourcesContent":["import type { Account } from '@gitlens/git/models/author.js';\nimport type { IntegrationIds } from '../constants.js';\nimport type { ProviderWarning } from '../results.js';\nimport { appendDedupedWarning } from '../results.js';\nimport type { ProviderReadContext } from './context.js';\nimport { runCaptured } from './drains.js';\nimport { noConnectionWarning, unsupportedWarning } from './warnings.js';\n\n/**\n * \"Who am I on this provider / connection\" — {@link IntegrationBase.getCurrentAccount}'s own answer, exposed on\n * the manager so a caller can ask for it directly instead of waiting for some other read to warm it as a side\n * effect.\n */\nexport interface CurrentAccountResult {\n\t/** The signed-in account, when the provider answered. */\n\taccount?: Account;\n\twarnings: ProviderWarning[];\n\t/**\n\t * Set whenever `account` is absent. Unlike a list/batch read, this one has no benign empty answer — \"who am\n\t * I\" is either resolved or it couldn't be told, never a legitimately empty result — so `account` is never\n\t * missing without a warning explaining why.\n\t */\n\tfetchFailed?: boolean;\n}\n\n/**\n * Resolves the viewer's own account for a connection. Jira Data Center refuses: it has no account-wide lookup.\n *\n * Goes THROUGH `Integration.getCurrentAccount`'s own cache (the hook a host supplies as\n * `IntegrationManagerCacheProvider.getCurrentAccount`) rather than adding a second one, so this read and\n * whatever else warms that cache — today, authorship enrichment on the pull request reads — stay one cache.\n */\nexport async function getCurrentAccount(\n\tctx: ProviderReadContext,\n\toptions: { providerId: IntegrationIds; connectionId?: string; domain?: string },\n): Promise<CurrentAccountResult> {\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\tif (early.warnings.length > 0) return { warnings: early.warnings, fetchFailed: true };\n\n\t\t// The untargeted primary path reads a disconnected provider as a silent empty result on the list reads; an\n\t\t// absent account must still carry a warning here, as on the batch reads — see `CurrentAccountResult.fetchFailed`.\n\t\tconst domain = ctx.resolveDomainForRead(options.providerId, options.connectionId, options.domain);\n\t\treturn { warnings: [noConnectionWarning(options.providerId, domain, options.connectionId)], fetchFailed: true };\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\n\tif (!integration.supportsCurrentAccount) {\n\t\treturn {\n\t\t\twarnings: [\n\t\t\t\tunsupportedWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t`Current account lookup is not supported by '${options.providerId}'.`,\n\t\t\t\t),\n\t\t\t],\n\t\t\tfetchFailed: true,\n\t\t};\n\t}\n\n\tconst { value: account, warning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\tasync () => {\n\t\t\tconst resolved = await integration.getCurrentAccount({ connectionId: options.connectionId });\n\t\t\t// `undefined` is returned bare (not `{ value: undefined }`) so `runCaptured` treats it the same as\n\t\t\t// a session it couldn't resolve — the only other way this read core answers nothing.\n\t\t\treturn resolved != null ? { value: resolved } : undefined;\n\t\t},\n\t\t{ warnOnMissingSession: true },\n\t);\n\n\tconst warnings: ProviderWarning[] = [];\n\tif (warning != null) {\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\n\treturn {\n\t\t...(account != null ? { account: account } : {}),\n\t\twarnings: warnings,\n\t\tfetchFailed: account == null ? true : undefined,\n\t};\n}\n"],"mappings":";;;;;;;;;;;AAgCA,eAAsB,kBACrB,KACA,SACgC;CAChC,MAAM,cAAc,MAAM,IAAI,sBAAsB,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAC5G,IAAI,eAAe,MAAM;EACxB,MAAM,QAAQ,IAAI,8BAA8B,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;EACxG,IAAI,MAAM,SAAS,SAAS,GAAG,OAAO;GAAE,UAAU,MAAM;GAAU,aAAa;EAAK;EAIpF,MAAM,SAAS,IAAI,qBAAqB,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;EAChG,OAAO;GAAE,UAAU,CAAC,oBAAoB,QAAQ,YAAY,QAAQ,QAAQ,YAAY,CAAC;GAAG,aAAa;EAAK;CAC/G;CAEA,MAAM,SAAS,IAAI,cAAc,aAAa,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAEtG,IAAI,CAAC,YAAY,wBAChB,OAAO;EACN,UAAU,CACT,mBACC,QAAQ,YACR,QACA,QAAQ,cACR,+CAA+C,QAAQ,WAAW,GACnE,CACD;EACA,aAAa;CACd;CAGD,MAAM,EAAE,OAAO,SAAS,YAAY,MAAM,YACzC,QAAQ,YACR,QACA,QAAQ,cACR,YAAY;EACX,MAAM,WAAW,MAAM,YAAY,kBAAkB,EAAE,cAAc,QAAQ,aAAa,CAAC;EAG3F,OAAO,YAAY,OAAO,EAAE,OAAO,SAAS,IAAI,KAAA;CACjD,GACA,EAAE,sBAAsB,KAAK,CAC9B;CAEA,MAAM,WAA8B,CAAC;CACrC,IAAI,WAAW,MACd,qBAAqB,UAAU,OAAO;CAGvC,OAAO;EACN,GAAI,WAAW,OAAO,EAAW,QAAQ,IAAI,CAAC;EACpC;EACV,aAAa,WAAW,OAAO,OAAO,KAAA;CACvC;AACD"}
@@ -91,6 +91,7 @@ async function listIssueTrackerIssuesPage(ctx, options) {
91
91
  return emptyPage(true);
92
92
  }
93
93
  await ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);
94
+ await integration.getCurrentAccount({ connectionId: options.connectionId }).catch(() => void 0);
94
95
  const { value: resources, warning: resourcesWarning } = await runCaptured(options.providerId, domain, options.connectionId, () => integration.getResourcesForUserResult(options.connectionId));
95
96
  if (resourcesWarning != null) warnings.push(resourcesWarning);
96
97
  if (resources == null || resources.length === 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"issueTracker.js","names":[],"sources":["../../../../../plus/integrations/src/reads/issueTracker.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport type { IssueShape, IssueSorting, IssueStateFilter } from '@gitlens/git/models/issue.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { mapBounded } from '@gitlens/utils/promise.js';\nimport { assessCollectionMetadata, mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { providerFanOutConcurrency } from '../constants.js';\nimport { toError } from '../errors.js';\nimport type { IntegrationResult } from '../models/integration.js';\nimport type { ProjectIssuesDrain } from '../models/issueReads.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport { IssueFilter, providersMetadata } from '../providers/models.js';\nimport { mergeCollectionMetadata, parsePageCursor } from '../providers/utils/providerPaging.js';\nimport type { ProviderPagedResult, ProviderWarning } from '../results.js';\nimport { appendDedupedWarning, reconcileOmissionsWithFailure } from '../results.js';\nimport { isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { parseIssueTrackerPageCursor, toIssueTrackerPageCursor } from './cursors.js';\nimport { runCaptured } from './drains.js';\nimport { projectKey, resourceIdForProject, resourceLabel, resourceMatchesOrg } from './hierarchy.utils.js';\nimport { resolveIssueSort, toIssueOrdering } from './ordering.js';\nimport {\n\tincompleteReadWarning,\n\tissueTrackerOnlySurfaceWarning,\n\totherWarning,\n\tunmergeableIssueSortWarning,\n\tunsupportedIssueSortWarning,\n\tunsupportedWarning,\n} from './warnings.js';\n\nconst issueStateFilters: readonly IssueStateFilter[] = ['open', 'closed', 'all'];\n\nexport async function listIssueTrackerIssuesPage(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\torg?: string;\n\t\tproject?: string;\n\t\tfilters?: IssueFilter[];\n\t\t/** Broadens the read to every assignee. Scopes to user-assigned issues when omitted. */\n\t\tincludeAllAssignees?: boolean;\n\t\t/**\n\t\t * How to order the issues, as `field:direction`. Omitted orders most-recently-updated-first where the\n\t\t * tracker can express it.\n\t\t *\n\t\t * Validated against `getSupportedFilters().issues`' sibling `issueSorts` — a tracker reports there, not\n\t\t * under the account-wide table, because resource -> project IS its only issue surface. A key it can't\n\t\t * express refuses the read rather than serving a differently-ordered list.\n\t\t *\n\t\t * One page can span SEVERAL projects, whose issues are merged here, so a key no normalized issue carries\n\t\t * (`priority`, `dueDate`, `resolved`) is refused for a multi-project page even though the tracker orders by\n\t\t * it perfectly well within one project.\n\t\t *\n\t\t * Unlike the git-host reads this one is safe to change mid-pagination: its cursor windows PROJECTS and\n\t\t * drains each to exhaustion, so which projects a round covers doesn't depend on how their issues are\n\t\t * ordered. Only the order within a page changes.\n\t\t */\n\t\tsort?: IssueSorting;\n\t\t/**\n\t\t * Which issue states to read. Omitted reads open issues only.\n\t\t *\n\t\t * Applied server-side by every project read, so it narrows each project's drain rather than the merged page.\n\t\t * Only a tracker declaring `ProviderMetadata.supportsIssueStates` can express anything but `'open'`; asking\n\t\t * another for `'closed'`/`'all'` refuses the read rather than serving its open issues.\n\t\t */\n\t\tstate?: IssueStateFilter;\n\t\tforceSync?: boolean;\n\t\tpage?: number;\n\t\tcursor?: string;\n\t\titemsPerPage?: number;\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed tracker host. Used to select the instance when the requested connection has no\n\t\t * configured domain; it must come from the trusted authentication configuration, never from repository\n\t\t * or remote data. Ignored for the cloud trackers.\n\t\t */\n\t\tdomain?: string;\n\t},\n): Promise<ProviderPagedResult<IssueShape>> {\n\t// Pagination is opt-in: only window the projects when the caller actually asked to page. A caller that\n\t// passes none of page/cursor/itemsPerPage keeps the \"aggregate every matched project\" contract, so an\n\t// existing consumer that doesn't inspect `hasMore` never silently loses projects past a default window.\n\tconst compositeCursor = parseIssueTrackerPageCursor(options.cursor);\n\tconst paginated =\n\t\tcompositeCursor?.unpaged !== true &&\n\t\t(options.page != null || options.cursor != null || options.itemsPerPage != null);\n\t// A composite cursor can carry retries from earlier project windows alongside the next untouched window.\n\t// `currentPage` remains the caller-facing position; `nextPage` is the window this round should advance.\n\tconst page = Math.max(\n\t\t1,\n\t\tMath.trunc(compositeCursor?.currentPage ?? parsePageCursor(options.cursor) ?? options.page ?? 1),\n\t);\n\tconst projectsPerPage = Math.max(1, Math.trunc(options.itemsPerPage ?? 20));\n\n\tconst items: IssueShape[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tconst emptyPage = (\n\t\tfetchFailed?: boolean,\n\t\ttruncated?: boolean,\n\t\tretry?: {\n\t\t\tnextPage?: number;\n\t\t\tpages?: readonly number[];\n\t\t\tprojects?: readonly string[];\n\t\t\tcompletedProjects?: readonly string[];\n\t\t},\n\t): ProviderPagedResult<IssueShape> => {\n\t\tconst cursor =\n\t\t\tretry != null\n\t\t\t\t? toIssueTrackerPageCursor({\n\t\t\t\t\t\tcurrentPage: page + 1,\n\t\t\t\t\t\tunpaged: !paginated,\n\t\t\t\t\t\tnextPage: retry.nextPage,\n\t\t\t\t\t\tretryPages: retry.pages ?? [],\n\t\t\t\t\t\tretryProjects: retry.projects ?? [],\n\t\t\t\t\t\tcompletedProjects: retry.completedProjects ?? [],\n\t\t\t\t\t\tstate: options.state,\n\t\t\t\t\t})\n\t\t\t\t: undefined;\n\t\treturn {\n\t\t\titems: items,\n\t\t\twarnings: warnings,\n\t\t\tpage: { currentPage: page, itemsPerPage: items.length, truncated: truncated || undefined },\n\t\t\t// Retry-only state is deliberately manual: advertising it as forward progress would make a\n\t\t\t// persistent provider failure spin forever in a normal `while (hasMore)` consumer. An untouched\n\t\t\t// project window remains normal forward progress even when this window failed completely.\n\t\t\thasMore: retry?.nextPage != null && cursor != null,\n\t\t\tcursor: cursor,\n\t\t\tfetchFailed: fetchFailed || undefined,\n\t\t};\n\t};\n\tconst normalWindowPage = paginated\n\t\t? (compositeCursor?.nextPage ?? (compositeCursor == null ? page : undefined))\n\t\t: undefined;\n\tconst priorRetryPages = compositeCursor?.retryPages ?? [];\n\tconst priorRetryProjects = compositeCursor?.retryProjects ?? [];\n\tconst priorCompletedProjects = compositeCursor?.completedProjects ?? [];\n\tconst trackCompletedProjects =\n\t\tpriorCompletedProjects.length > 0 || priorRetryPages.length > 0 || compositeCursor?.completedProjects != null;\n\tconst retryPagesForDiscoveryFailure = (): number[] => [\n\t\t...priorRetryPages,\n\t\t...(normalWindowPage != null ? [normalWindowPage] : compositeCursor == null ? [1] : []),\n\t];\n\tconst discoveryFailureRetry = (): {\n\t\tpages: readonly number[];\n\t\tprojects: readonly string[];\n\t\tcompletedProjects: readonly string[];\n\t} => ({\n\t\tpages: retryPagesForDiscoveryFailure(),\n\t\tprojects: priorRetryProjects,\n\t\tcompletedProjects: priorCompletedProjects,\n\t});\n\n\tif (!isIssuesHostIntegrationId(options.providerId)) {\n\t\twarnings.push(\n\t\t\tissueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, 'Issue-tracker project reads'),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connectionId or domain that no longer resolves is a broken target, not an empty account.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\twarnings.push(...early.warnings);\n\t\treturn emptyPage(early.fetchFailed);\n\t}\n\tif (!isIssuesIntegration(integration)) {\n\t\twarnings.push(\n\t\t\tissueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, 'Issue-tracker project reads'),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\n\t// Before any upstream request: the discovery fan-outs below (resources, projects, per-resource accounts) are\n\t// three round trips, and an order this tracker can't express refuses the read whatever they return. Placed after\n\t// `domain` only because every warning carries it.\n\tconst resolvedSort = resolveIssueSort(providersMetadata[options.providerId]?.supportedIssueSorts, options.sort);\n\tif (resolvedSort.rejection != null) {\n\t\twarnings.push(\n\t\t\tunsupportedIssueSortWarning(options.providerId, domain, options.connectionId, resolvedSort.rejection),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst sort = resolvedSort.sort;\n\n\t// Refused before any request for the same reason as the sort: a state the tracker can't express would otherwise\n\t// be dropped by its project reads, and their open issues published as the requested state. A value outside the\n\t// vocabulary is refused too, since `toProviderIssueStates` would read it as omitted, which means open.\n\tif (\n\t\toptions.state != null &&\n\t\t(!issueStateFilters.includes(options.state) ||\n\t\t\t(options.state !== 'open' && !providersMetadata[options.providerId]?.supportsIssueStates))\n\t) {\n\t\t// A known state this tracker can't express is an unsupported capability; a value outside the vocabulary is\n\t\t// caller input the caller fixes in the request.\n\t\twarnings.push(\n\t\t\tissueStateFilters.includes(options.state)\n\t\t\t\t? unsupportedWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Issue state '${options.state}' is not supported by '${options.providerId}'.`,\n\t\t\t\t\t)\n\t\t\t\t: otherWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Unknown issue state; expected one of ${issueStateFilters.join(', ')}.`,\n\t\t\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// A cursor resumed under another state than it was minted for skips what it already covered, whose other-state\n\t// issues were never read. A narrowed read only mints composite cursors, which record their state, so any other\n\t// cursor came from an open read.\n\tconst cursorState =\n\t\tcompositeCursor != null ? (compositeCursor.state ?? 'open') : options.cursor != null ? 'open' : undefined;\n\tif (cursorState != null && cursorState !== (options.state ?? 'open')) {\n\t\twarnings.push(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'The cursor belongs to a read of another issue state; start again without it.',\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\tconst { value: resources, warning: resourcesWarning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() => integration.getResourcesForUserResult(options.connectionId),\n\t);\n\tif (resourcesWarning != null) {\n\t\twarnings.push(resourcesWarning);\n\t}\n\tif (resources == null || resources.length === 0) {\n\t\tconst fetchFailed = resourcesWarning != null && resources == null;\n\t\treturn emptyPage(fetchFailed, false, fetchFailed ? discoveryFailureRetry() : undefined);\n\t}\n\n\tconst scopedResources =\n\t\toptions.org != null ? resources.filter(r => resourceMatchesOrg(r, options.org!)) : resources;\n\tif (scopedResources.length === 0) {\n\t\treturn emptyPage();\n\t}\n\n\tconst { value: projectsResult, warning: projectsWarning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() => integration.getProjectsForResourcesWithMetadataResult(scopedResources, options.connectionId),\n\t);\n\tif (projectsWarning != null) {\n\t\twarnings.push(projectsWarning);\n\t}\n\t// Partial project discovery: continue with the resources that succeeded, but surface per-resource\n\t// failures as warnings and remember to mark the page fetchFailed so the caller knows some issues may be\n\t// missing. `projectDiscoveryFailed`/`projectDiscoveryTruncated` are OR-ed into the page's\n\t// fetchFailed/truncated at every return below (a truncated-but-not-failed discovery, e.g. a paging\n\t// backstop, still means the project set is incomplete).\n\tconst projectDiscoveryAssessment = mergeAssessmentInto(\n\t\twarnings,\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\tprojectsResult?.metadata,\n\t);\n\tconst projectDiscoveryFailed = projectDiscoveryAssessment.fetchFailed;\n\tconst projectDiscoveryTruncated = projectDiscoveryAssessment.truncated;\n\tconst projects = projectsResult?.values;\n\tif (projects == null || projects.length === 0) {\n\t\tconst fetchFailed = (projectsWarning != null && projectsResult == null) || projectDiscoveryFailed;\n\t\treturn emptyPage(\n\t\t\tfetchFailed,\n\t\t\tprojectDiscoveryTruncated,\n\t\t\tfetchFailed || projectDiscoveryTruncated ? discoveryFailureRetry() : undefined,\n\t\t);\n\t}\n\n\tconst matchedProjects =\n\t\toptions.project != null ? projects.filter(p => resourceMatchesOrg(p, options.project!)) : projects;\n\n\t// Validate the requested filters against what this provider supports (e.g. Linear/Trello support only\n\t// Assignee). An unsupported filter must not silently degrade — Linear/Trello ignore the requested type\n\t// and apply Assignee regardless — so warn + fetchFailed instead of returning a differently-scoped set.\n\tif (options.filters?.length) {\n\t\tconst supported = providersMetadata[options.providerId]?.supportedIssueFilters;\n\t\tconst allSupported = supported != null && options.filters.every(f => supported.includes(f));\n\t\tif (!allSupported) {\n\t\t\twarnings.push(\n\t\t\t\tunsupportedWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t`One or more requested issue filters are not supported by '${options.providerId}'.`,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn emptyPage(true);\n\t\t}\n\t}\n\n\t// `includeAllAssignees` drops the user scope, but a user-relative filter (Author/Mention) is meaningless\n\t// without a user. Passing both to the provider degrades silently: Jira, seeing no user, falls through to\n\t// an unscoped project fetch and returns EVERY issue instead of the requested author's/mentions. Reject\n\t// the incompatible combination up front rather than publishing a differently-scoped set as the result.\n\tif (options.includeAllAssignees === true && options.filters?.some(f => f !== IssueFilter.Assignee)) {\n\t\twarnings.push(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`\\`includeAllAssignees\\` cannot be combined with an author/mention filter for '${options.providerId}' (those filters require a user scope).`,\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// Scope to the current user's assigned issues unless the caller broadens to all assignees. Resolve the\n\t// handle from each resource's own account (multi-account safe), capturing any error so its kind\n\t// (e.g. auth) is preserved rather than collapsed to a generic warning.\n\tlet usersByResourceId: Map<string, string> | undefined;\n\t// The same accounts keyed by the same resource, holding the provider's stable id rather than the handle. A\n\t// separate map rather than a widened value because the two are resolved and consumed independently: a\n\t// provider may carry an account with no id, and `scopedProjectsWithUsers` gates on the HANDLE alone, so\n\t// folding them together would drop a resource whose id happened to be missing.\n\tlet userIdsByResourceId: Map<string, string> | undefined;\n\tlet accountLookupFailed = false;\n\tif (options.includeAllAssignees !== true) {\n\t\tusersByResourceId = new Map<string, string>();\n\t\tuserIdsByResourceId = new Map<string, string>();\n\t\tconst accounts = await mapBounded(scopedResources, providerFanOutConcurrency, async resource => ({\n\t\t\tresource: resource,\n\t\t\t...(await runCaptured(options.providerId, domain, options.connectionId, () =>\n\t\t\t\tintegration.getAccountForResourceResult(resource, options.connectionId),\n\t\t\t)),\n\t\t}));\n\n\t\tfor (const { resource, value: account, warning: accountWarning } of accounts) {\n\t\t\tconst user = account?.username ?? account?.name ?? undefined;\n\t\t\tif (user != null) {\n\t\t\t\tconst resourceKey = resourceIdForProject(resource) ?? resource.key;\n\t\t\t\tusersByResourceId.set(resourceKey, user);\n\t\t\t\t// Empty-checked, not just null-checked: an account shape declares `id` as a plain string, and a\n\t\t\t\t// provider with nothing to put there sets `''` rather than omitting it — which would otherwise\n\t\t\t\t// reach a user-field query as an empty identity and match nothing.\n\t\t\t\tif (account?.id != null && account.id !== '') {\n\t\t\t\t\tuserIdsByResourceId.set(resourceKey, account.id);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Deduped: a captured `accountWarning` carries only the provider error, with no resource in its\n\t\t\t// message, so one expired token across N resources would otherwise repeat verbatim N times. The\n\t\t\t// synthesized fallback names the resource, so those stay distinct on their own.\n\t\t\tappendDedupedWarning(\n\t\t\t\twarnings,\n\t\t\t\taccountWarning ??\n\t\t\t\t\totherWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Could not resolve the current user for '${resourceLabel(resource)}'; skipping that resource to avoid returning issues assigned to others.`,\n\t\t\t\t\t),\n\t\t\t);\n\t\t\taccountLookupFailed = true;\n\t\t}\n\t}\n\n\tconst fallbackUserForUnscopedProject =\n\t\tscopedResources.length === 1 && usersByResourceId?.size === 1\n\t\t\t? usersByResourceId.values().next().value\n\t\t\t: undefined;\n\tconst userForProject = (project: ResourceDescriptor): string | undefined => {\n\t\tconst resourceId = resourceIdForProject(project);\n\t\tif (resourceId != null) {\n\t\t\tconst user = usersByResourceId?.get(resourceId);\n\t\t\tif (user != null) {\n\t\t\t\treturn user;\n\t\t\t}\n\t\t}\n\n\t\t// Some providers/tests return project descriptors without their parent resource id. When we have only\n\t\t// one scoped resource, re-use that sole resolved user rather than silently dropping every project.\n\t\treturn fallbackUserForUnscopedProject;\n\t};\n\t// Deliberately NOT falling back the way `userForProject` does. That fallback exists so a project with no\n\t// parent resource id still gets scoped rather than dropped, and the handle is what the drop is judged on;\n\t// the id is optional everywhere it is read, so a miss degrades to the handle instead of widening the read.\n\tconst userIdForProject = (project: ResourceDescriptor): string | undefined => {\n\t\tconst resourceId = resourceIdForProject(project);\n\t\treturn resourceId != null ? userIdsByResourceId?.get(resourceId) : undefined;\n\t};\n\n\tconst retryProjectKeys = new Set<string>();\n\tconst completedProjectKeys = new Set(priorCompletedProjects);\n\tfor (const retryProject of priorRetryProjects) {\n\t\tcompletedProjectKeys.delete(retryProject);\n\t}\n\tconst scopedProjectsWithUsers =\n\t\tusersByResourceId != null\n\t\t\t? matchedProjects.filter(project => {\n\t\t\t\t\tif (userForProject(project) != null) return true;\n\n\t\t\t\t\tconst key = projectKey(project);\n\t\t\t\t\tretryProjectKeys.add(key);\n\t\t\t\t\tcompletedProjectKeys.delete(key);\n\t\t\t\t\treturn false;\n\t\t\t\t})\n\t\t\t: matchedProjects;\n\n\t// Select the untouched window plus any earlier windows/projects that explicitly need retrying. Keys\n\t// include the parent resource so two trackers can reuse the same project id without collapsing.\n\tconst projectsByRetryKey = new Map(scopedProjectsWithUsers.map(project => [projectKey(project), project]));\n\tconst scopedProjectsByKey = new Map<string, ResourceDescriptor>();\n\tconst addScopedProject = (project: ResourceDescriptor): void => {\n\t\tconst key = projectKey(project);\n\t\tif (completedProjectKeys.has(key)) return;\n\n\t\tscopedProjectsByKey.set(key, project);\n\t};\n\tfor (const retryProject of priorRetryProjects) {\n\t\tconst project = projectsByRetryKey.get(retryProject);\n\t\tif (project != null) {\n\t\t\taddScopedProject(project);\n\t\t} else if (projectDiscoveryFailed || projectDiscoveryTruncated) {\n\t\t\tretryProjectKeys.add(retryProject);\n\t\t}\n\t}\n\tconst windowPages = new Set<number>(priorRetryPages);\n\tif (normalWindowPage != null) {\n\t\twindowPages.add(normalWindowPage);\n\t}\n\tif (!paginated && (projectDiscoveryFailed || projectDiscoveryTruncated)) {\n\t\twindowPages.add(1);\n\t}\n\tif (paginated) {\n\t\tfor (const windowPage of windowPages) {\n\t\t\tconst windowStart = (windowPage - 1) * projectsPerPage;\n\t\t\tfor (const project of scopedProjectsWithUsers.slice(windowStart, windowStart + projectsPerPage)) {\n\t\t\t\taddScopedProject(project);\n\t\t\t}\n\t\t}\n\t} else if (compositeCursor == null || priorRetryPages.length > 0) {\n\t\t// An unpaged retry-only cursor should re-read only the explicitly failed projects already added\n\t\t// above. Discovery retries still rescan the aggregate set so newly recovered projects are included.\n\t\tfor (const project of scopedProjectsWithUsers) {\n\t\t\taddScopedProject(project);\n\t\t}\n\t}\n\tconst scopedProjects = [...scopedProjectsByKey.values()];\n\tconst furthestWindowPage = windowPages.size > 0 ? Math.max(...windowPages) : normalWindowPage;\n\tconst normalWindowEnd =\n\t\tfurthestWindowPage != null ? furthestWindowPage * projectsPerPage : scopedProjectsWithUsers.length;\n\tconst nextPage =\n\t\tpaginated && furthestWindowPage != null && scopedProjectsWithUsers.length > normalWindowEnd\n\t\t\t? furthestWindowPage + 1\n\t\t\t: undefined;\n\tconst retryWindowPages = (): number[] => {\n\t\tif (!projectDiscoveryFailed && !projectDiscoveryTruncated && retryProjectKeys.size === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst pages = new Set(windowPages);\n\t\tif (pages.size === 0) {\n\t\t\t// A terminal retry has no untouched next window to identify its origin. Keep a manual window\n\t\t\t// marker so a later partial discovery can reconcile against the projects already emitted.\n\t\t\tpages.add(paginated ? Math.max(1, page - 1) : 1);\n\t\t}\n\t\treturn [...pages];\n\t};\n\tif (scopedProjects.length === 0) {\n\t\t// The discovered projects didn't intersect the requested filter/window, or every matching resource\n\t\t// failed user resolution. If discovery or account lookup was partial, the empty result is not a\n\t\t// proven-empty account — carry `fetchFailed` so the caller knows issues may be missing.\n\t\treturn emptyPage(projectDiscoveryFailed || accountLookupFailed, projectDiscoveryTruncated, {\n\t\t\tnextPage: nextPage,\n\t\t\tpages: retryWindowPages(),\n\t\t\tprojects: [...retryProjectKeys],\n\t\t\tcompletedProjects: [...completedProjectKeys],\n\t\t});\n\t}\n\n\t// A read spanning several projects is a merge, so it can only honor a key a normalized issue carries. Bound here\n\t// rather than next to the key's validation above, because the project count is only known now — while still\n\t// before any project read, so a refusal costs no request. Refused rather than served as concatenated\n\t// per-project runs, which would look ordered without being so.\n\t//\n\t// Counted over every project the READ covers, not the `scopedProjects` window this page happens to hold: the\n\t// window is `itemsPerPage` projects wide, so counting it would refuse a 20-project page and then serve the\n\t// final 1-project page of the same paginated call — the same key answered two ways, with the earlier pages'\n\t// issues missing from a read that reported success.\n\t//\n\t// Projects are not the only thing a tracker read merges: a user-scoped read asking for several relationships\n\t// issues one provider query PER relationship and unions them within each project (Jira runs one JQL drain per\n\t// filter and folds them into one map, in filter order), so a single-project page of two filters is just as\n\t// concatenated as a two-project one. Counting only projects published those runs under the requested key.\n\t// Not gated on the provider: the two trackers that don't fan out (Linear, Trello) declare `Assignee` as their\n\t// only supported filter, so a multi-filter read is already refused above and can never reach this.\n\tconst mergesFilters = options.includeAllAssignees !== true && (options.filters?.length ?? 0) > 1;\n\tconst ordering = toIssueOrdering(sort, scopedProjectsWithUsers.length > 1 || mergesFilters);\n\tif (ordering.unmergeable != null) {\n\t\twarnings.push(\n\t\t\tunmergeableIssueSortWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tordering.unmergeable,\n\t\t\t\tscopedProjectsWithUsers.length > 1 ? 'projects' : 'filters',\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// One call for the whole window, so a tracker that can search several projects at once (Jira) does, instead\n\t// of paying one drain per project. Results still come back per project, which the accounting below needs.\n\tlet projectReads: IntegrationResult<ProjectIssuesDrain | undefined>[];\n\ttry {\n\t\tprojectReads = await integration.getIssuesForProjectsWithTruncationResult(\n\t\t\tscopedProjects.map(project => ({\n\t\t\t\tproject: project,\n\t\t\t\toptions: {\n\t\t\t\t\tuser: userForProject(project),\n\t\t\t\t\tuserId: userIdForProject(project),\n\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\tsort: sort,\n\t\t\t\t\tstate: options.state,\n\t\t\t\t},\n\t\t\t})),\n\t\t\toptions.connectionId,\n\t\t);\n\t} catch (ex) {\n\t\tconst error = toError(ex);\n\t\tprojectReads = scopedProjects.map(() => ({ error: error }));\n\t}\n\tconst perProject = await Promise.all(\n\t\tscopedProjects.map(async (project, index) => ({\n\t\t\tproject: project,\n\t\t\t...(await runCaptured(options.providerId, domain, options.connectionId, () =>\n\t\t\t\tPromise.resolve(projectReads[index]),\n\t\t\t)),\n\t\t})),\n\t);\n\n\t// Partial project discovery means some projects' issues are missing from this page; propagate it so the\n\t// page reports fetchFailed even when every discovered project's own read succeeded.\n\tlet fetchFailed = projectDiscoveryFailed || accountLookupFailed;\n\tlet truncationRecovery: 'narrow-scope' | 'none' | undefined = projectDiscoveryTruncated ? 'none' : undefined;\n\tlet drainMetadata: CollectionMetadata | undefined;\n\tfor (const { project, value: result, warning } of perProject) {\n\t\tconst key = projectKey(project);\n\t\tconst retryProject = result == null;\n\t\tif (warning != null) {\n\t\t\t// Deduped: the per-project warning is built from the provider error alone and names no project, so a\n\t\t\t// single failing token repeats verbatim once per project in the window.\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t\tfetchFailed = true;\n\t\t\ttruncationRecovery = 'none';\n\t\t}\n\t\t// A thrown/unsupported read (e.g. Linear not-implemented) surfaces as a warning with no value;\n\t\t// mark the aggregate as fetchFailed so an empty result isn't mistaken for \"no issues\".\n\t\tif (result == null) {\n\t\t\tfetchFailed = true;\n\t\t}\n\t\tif (result != null) {\n\t\t\titems.push(...result.values);\n\t\t\tif (result.truncated) {\n\t\t\t\tconst recovery = result.recovery;\n\t\t\t\ttruncationRecovery = truncationRecovery == null || truncationRecovery === recovery ? recovery : 'none';\n\t\t\t}\n\t\t\tif (result.metadata != null) {\n\t\t\t\tdrainMetadata = mergeCollectionMetadata(drainMetadata, result.metadata);\n\t\t\t}\n\t\t}\n\t\tif (retryProject) {\n\t\t\tretryProjectKeys.add(key);\n\t\t\tcompletedProjectKeys.delete(key);\n\t\t} else {\n\t\t\t// A usable partial/truncated project is emitted once and remains structurally incomplete.\n\t\t\t// Re-running the same project cursor would normally return the same prefix and duplicate it\n\t\t\t// across facade pages; only a project that returned no value is safe to retry automatically.\n\t\t\tcompletedProjectKeys.add(key);\n\t\t}\n\t}\n\n\t// Each provider query arrived ordered by the tracker; the union of them — across projects, and across the\n\t// per-relationship queries a multi-filter read fans out into — is not, and the union is what this read\n\t// publishes. A no-op when nothing merged, since the tracker already ordered that single run.\n\tconst orderedItems = ordering.order(items);\n\n\tconst projectReadReportedTruncation = truncationRecovery != null;\n\tconst drainAssessment = assessCollectionMetadata(options.providerId, domain, options.connectionId, drainMetadata);\n\tfor (const warning of drainAssessment.warnings) {\n\t\t// A project result already supplies the recovery semantics. Replace metadata's unstructured fallback with\n\t\t// the structured warning below, while retaining specific failures and omissions.\n\t\tif (projectReadReportedTruncation && !drainAssessment.fetchFailed && warning.omission == null) continue;\n\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\tfetchFailed = fetchFailed || drainAssessment.fetchFailed;\n\tif (drainAssessment.truncated) {\n\t\ttruncationRecovery = 'none';\n\t}\n\n\tconst projectTruncated = truncationRecovery != null;\n\tconst scopeTooLarge = !fetchFailed && truncationRecovery === 'narrow-scope';\n\tif (scopeTooLarge || (!fetchFailed && projectTruncated && !warnings.some(warning => warning.omission != null))) {\n\t\tappendDedupedWarning(\n\t\t\twarnings,\n\t\t\tincompleteReadWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tscopeTooLarge\n\t\t\t\t\t? 'Some projects hold more issues than one read can return; narrow the scope to read the rest.'\n\t\t\t\t\t: 'Some issues were omitted; the provider returned an incomplete result.',\n\t\t\t\tscopeTooLarge ? 'scope-too-large' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier project asserts the read succeeded; a later one may since have failed.\n\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\n\tconst retryPages = retryWindowPages();\n\tconst cursor = toIssueTrackerPageCursor({\n\t\tcurrentPage: page + 1,\n\t\tunpaged: !paginated,\n\t\tnextPage: nextPage,\n\t\tretryPages: retryPages,\n\t\tretryProjects: [...retryProjectKeys],\n\t\tcompletedProjects:\n\t\t\t(trackCompletedProjects ||\n\t\t\t\tprojectDiscoveryFailed ||\n\t\t\t\tprojectDiscoveryTruncated ||\n\t\t\t\tretryProjectKeys.size > 0) &&\n\t\t\t(retryPages.length > 0 || nextPage != null)\n\t\t\t\t? [...completedProjectKeys]\n\t\t\t\t: [],\n\t\tstate: options.state,\n\t});\n\treturn {\n\t\titems: orderedItems,\n\t\twarnings: warnings,\n\t\tpage: { currentPage: page, itemsPerPage: orderedItems.length, truncated: projectTruncated || undefined },\n\t\t// Failed-project retries alone are manual. Only an untouched project window is automatic progress.\n\t\thasMore: nextPage != null && cursor != null,\n\t\tcursor: cursor,\n\t\tfetchFailed: fetchFailed || undefined,\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"issueTracker.js","names":[],"sources":["../../../../../plus/integrations/src/reads/issueTracker.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport type { IssueShape, IssueSorting, IssueStateFilter } from '@gitlens/git/models/issue.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { mapBounded } from '@gitlens/utils/promise.js';\nimport { assessCollectionMetadata, mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { providerFanOutConcurrency } from '../constants.js';\nimport { toError } from '../errors.js';\nimport type { IntegrationResult } from '../models/integration.js';\nimport type { ProjectIssuesDrain } from '../models/issueReads.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport { IssueFilter, providersMetadata } from '../providers/models.js';\nimport { mergeCollectionMetadata, parsePageCursor } from '../providers/utils/providerPaging.js';\nimport type { ProviderPagedResult, ProviderWarning } from '../results.js';\nimport { appendDedupedWarning, reconcileOmissionsWithFailure } from '../results.js';\nimport { isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { parseIssueTrackerPageCursor, toIssueTrackerPageCursor } from './cursors.js';\nimport { runCaptured } from './drains.js';\nimport { projectKey, resourceIdForProject, resourceLabel, resourceMatchesOrg } from './hierarchy.utils.js';\nimport { resolveIssueSort, toIssueOrdering } from './ordering.js';\nimport {\n\tincompleteReadWarning,\n\tissueTrackerOnlySurfaceWarning,\n\totherWarning,\n\tunmergeableIssueSortWarning,\n\tunsupportedIssueSortWarning,\n\tunsupportedWarning,\n} from './warnings.js';\n\nconst issueStateFilters: readonly IssueStateFilter[] = ['open', 'closed', 'all'];\n\nexport async function listIssueTrackerIssuesPage(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\torg?: string;\n\t\tproject?: string;\n\t\tfilters?: IssueFilter[];\n\t\t/** Broadens the read to every assignee. Scopes to user-assigned issues when omitted. */\n\t\tincludeAllAssignees?: boolean;\n\t\t/**\n\t\t * How to order the issues, as `field:direction`. Omitted orders most-recently-updated-first where the\n\t\t * tracker can express it.\n\t\t *\n\t\t * Validated against `getSupportedFilters().issues`' sibling `issueSorts` — a tracker reports there, not\n\t\t * under the account-wide table, because resource -> project IS its only issue surface. A key it can't\n\t\t * express refuses the read rather than serving a differently-ordered list.\n\t\t *\n\t\t * One page can span SEVERAL projects, whose issues are merged here, so a key no normalized issue carries\n\t\t * (`priority`, `dueDate`, `resolved`) is refused for a multi-project page even though the tracker orders by\n\t\t * it perfectly well within one project.\n\t\t *\n\t\t * Unlike the git-host reads this one is safe to change mid-pagination: its cursor windows PROJECTS and\n\t\t * drains each to exhaustion, so which projects a round covers doesn't depend on how their issues are\n\t\t * ordered. Only the order within a page changes.\n\t\t */\n\t\tsort?: IssueSorting;\n\t\t/**\n\t\t * Which issue states to read. Omitted reads open issues only.\n\t\t *\n\t\t * Applied server-side by every project read, so it narrows each project's drain rather than the merged page.\n\t\t * Only a tracker declaring `ProviderMetadata.supportsIssueStates` can express anything but `'open'`; asking\n\t\t * another for `'closed'`/`'all'` refuses the read rather than serving its open issues.\n\t\t */\n\t\tstate?: IssueStateFilter;\n\t\tforceSync?: boolean;\n\t\tpage?: number;\n\t\tcursor?: string;\n\t\titemsPerPage?: number;\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed tracker host. Used to select the instance when the requested connection has no\n\t\t * configured domain; it must come from the trusted authentication configuration, never from repository\n\t\t * or remote data. Ignored for the cloud trackers.\n\t\t */\n\t\tdomain?: string;\n\t},\n): Promise<ProviderPagedResult<IssueShape>> {\n\t// Pagination is opt-in: only window the projects when the caller actually asked to page. A caller that\n\t// passes none of page/cursor/itemsPerPage keeps the \"aggregate every matched project\" contract, so an\n\t// existing consumer that doesn't inspect `hasMore` never silently loses projects past a default window.\n\tconst compositeCursor = parseIssueTrackerPageCursor(options.cursor);\n\tconst paginated =\n\t\tcompositeCursor?.unpaged !== true &&\n\t\t(options.page != null || options.cursor != null || options.itemsPerPage != null);\n\t// A composite cursor can carry retries from earlier project windows alongside the next untouched window.\n\t// `currentPage` remains the caller-facing position; `nextPage` is the window this round should advance.\n\tconst page = Math.max(\n\t\t1,\n\t\tMath.trunc(compositeCursor?.currentPage ?? parsePageCursor(options.cursor) ?? options.page ?? 1),\n\t);\n\tconst projectsPerPage = Math.max(1, Math.trunc(options.itemsPerPage ?? 20));\n\n\tconst items: IssueShape[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tconst emptyPage = (\n\t\tfetchFailed?: boolean,\n\t\ttruncated?: boolean,\n\t\tretry?: {\n\t\t\tnextPage?: number;\n\t\t\tpages?: readonly number[];\n\t\t\tprojects?: readonly string[];\n\t\t\tcompletedProjects?: readonly string[];\n\t\t},\n\t): ProviderPagedResult<IssueShape> => {\n\t\tconst cursor =\n\t\t\tretry != null\n\t\t\t\t? toIssueTrackerPageCursor({\n\t\t\t\t\t\tcurrentPage: page + 1,\n\t\t\t\t\t\tunpaged: !paginated,\n\t\t\t\t\t\tnextPage: retry.nextPage,\n\t\t\t\t\t\tretryPages: retry.pages ?? [],\n\t\t\t\t\t\tretryProjects: retry.projects ?? [],\n\t\t\t\t\t\tcompletedProjects: retry.completedProjects ?? [],\n\t\t\t\t\t\tstate: options.state,\n\t\t\t\t\t})\n\t\t\t\t: undefined;\n\t\treturn {\n\t\t\titems: items,\n\t\t\twarnings: warnings,\n\t\t\tpage: { currentPage: page, itemsPerPage: items.length, truncated: truncated || undefined },\n\t\t\t// Retry-only state is deliberately manual: advertising it as forward progress would make a\n\t\t\t// persistent provider failure spin forever in a normal `while (hasMore)` consumer. An untouched\n\t\t\t// project window remains normal forward progress even when this window failed completely.\n\t\t\thasMore: retry?.nextPage != null && cursor != null,\n\t\t\tcursor: cursor,\n\t\t\tfetchFailed: fetchFailed || undefined,\n\t\t};\n\t};\n\tconst normalWindowPage = paginated\n\t\t? (compositeCursor?.nextPage ?? (compositeCursor == null ? page : undefined))\n\t\t: undefined;\n\tconst priorRetryPages = compositeCursor?.retryPages ?? [];\n\tconst priorRetryProjects = compositeCursor?.retryProjects ?? [];\n\tconst priorCompletedProjects = compositeCursor?.completedProjects ?? [];\n\tconst trackCompletedProjects =\n\t\tpriorCompletedProjects.length > 0 || priorRetryPages.length > 0 || compositeCursor?.completedProjects != null;\n\tconst retryPagesForDiscoveryFailure = (): number[] => [\n\t\t...priorRetryPages,\n\t\t...(normalWindowPage != null ? [normalWindowPage] : compositeCursor == null ? [1] : []),\n\t];\n\tconst discoveryFailureRetry = (): {\n\t\tpages: readonly number[];\n\t\tprojects: readonly string[];\n\t\tcompletedProjects: readonly string[];\n\t} => ({\n\t\tpages: retryPagesForDiscoveryFailure(),\n\t\tprojects: priorRetryProjects,\n\t\tcompletedProjects: priorCompletedProjects,\n\t});\n\n\tif (!isIssuesHostIntegrationId(options.providerId)) {\n\t\twarnings.push(\n\t\t\tissueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, 'Issue-tracker project reads'),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connectionId or domain that no longer resolves is a broken target, not an empty account.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\twarnings.push(...early.warnings);\n\t\treturn emptyPage(early.fetchFailed);\n\t}\n\tif (!isIssuesIntegration(integration)) {\n\t\twarnings.push(\n\t\t\tissueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, 'Issue-tracker project reads'),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\n\t// Before any upstream request: the discovery fan-outs below (resources, projects, per-resource accounts) are\n\t// three round trips, and an order this tracker can't express refuses the read whatever they return. Placed after\n\t// `domain` only because every warning carries it.\n\tconst resolvedSort = resolveIssueSort(providersMetadata[options.providerId]?.supportedIssueSorts, options.sort);\n\tif (resolvedSort.rejection != null) {\n\t\twarnings.push(\n\t\t\tunsupportedIssueSortWarning(options.providerId, domain, options.connectionId, resolvedSort.rejection),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst sort = resolvedSort.sort;\n\n\t// Refused before any request for the same reason as the sort: a state the tracker can't express would otherwise\n\t// be dropped by its project reads, and their open issues published as the requested state. A value outside the\n\t// vocabulary is refused too, since `toProviderIssueStates` would read it as omitted, which means open.\n\tif (\n\t\toptions.state != null &&\n\t\t(!issueStateFilters.includes(options.state) ||\n\t\t\t(options.state !== 'open' && !providersMetadata[options.providerId]?.supportsIssueStates))\n\t) {\n\t\t// A known state this tracker can't express is an unsupported capability; a value outside the vocabulary is\n\t\t// caller input the caller fixes in the request.\n\t\twarnings.push(\n\t\t\tissueStateFilters.includes(options.state)\n\t\t\t\t? unsupportedWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Issue state '${options.state}' is not supported by '${options.providerId}'.`,\n\t\t\t\t\t)\n\t\t\t\t: otherWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Unknown issue state; expected one of ${issueStateFilters.join(', ')}.`,\n\t\t\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// A cursor resumed under another state than it was minted for skips what it already covered, whose other-state\n\t// issues were never read. A narrowed read only mints composite cursors, which record their state, so any other\n\t// cursor came from an open read.\n\tconst cursorState =\n\t\tcompositeCursor != null ? (compositeCursor.state ?? 'open') : options.cursor != null ? 'open' : undefined;\n\tif (cursorState != null && cursorState !== (options.state ?? 'open')) {\n\t\twarnings.push(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'The cursor belongs to a read of another issue state; start again without it.',\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\tawait integration.getCurrentAccount({ connectionId: options.connectionId }).catch(() => undefined);\n\n\tconst { value: resources, warning: resourcesWarning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() => integration.getResourcesForUserResult(options.connectionId),\n\t);\n\tif (resourcesWarning != null) {\n\t\twarnings.push(resourcesWarning);\n\t}\n\tif (resources == null || resources.length === 0) {\n\t\tconst fetchFailed = resourcesWarning != null && resources == null;\n\t\treturn emptyPage(fetchFailed, false, fetchFailed ? discoveryFailureRetry() : undefined);\n\t}\n\n\tconst scopedResources =\n\t\toptions.org != null ? resources.filter(r => resourceMatchesOrg(r, options.org!)) : resources;\n\tif (scopedResources.length === 0) {\n\t\treturn emptyPage();\n\t}\n\n\tconst { value: projectsResult, warning: projectsWarning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() => integration.getProjectsForResourcesWithMetadataResult(scopedResources, options.connectionId),\n\t);\n\tif (projectsWarning != null) {\n\t\twarnings.push(projectsWarning);\n\t}\n\t// Partial project discovery: continue with the resources that succeeded, but surface per-resource\n\t// failures as warnings and remember to mark the page fetchFailed so the caller knows some issues may be\n\t// missing. `projectDiscoveryFailed`/`projectDiscoveryTruncated` are OR-ed into the page's\n\t// fetchFailed/truncated at every return below (a truncated-but-not-failed discovery, e.g. a paging\n\t// backstop, still means the project set is incomplete).\n\tconst projectDiscoveryAssessment = mergeAssessmentInto(\n\t\twarnings,\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\tprojectsResult?.metadata,\n\t);\n\tconst projectDiscoveryFailed = projectDiscoveryAssessment.fetchFailed;\n\tconst projectDiscoveryTruncated = projectDiscoveryAssessment.truncated;\n\tconst projects = projectsResult?.values;\n\tif (projects == null || projects.length === 0) {\n\t\tconst fetchFailed = (projectsWarning != null && projectsResult == null) || projectDiscoveryFailed;\n\t\treturn emptyPage(\n\t\t\tfetchFailed,\n\t\t\tprojectDiscoveryTruncated,\n\t\t\tfetchFailed || projectDiscoveryTruncated ? discoveryFailureRetry() : undefined,\n\t\t);\n\t}\n\n\tconst matchedProjects =\n\t\toptions.project != null ? projects.filter(p => resourceMatchesOrg(p, options.project!)) : projects;\n\n\t// Validate the requested filters against what this provider supports (e.g. Linear/Trello support only\n\t// Assignee). An unsupported filter must not silently degrade — Linear/Trello ignore the requested type\n\t// and apply Assignee regardless — so warn + fetchFailed instead of returning a differently-scoped set.\n\tif (options.filters?.length) {\n\t\tconst supported = providersMetadata[options.providerId]?.supportedIssueFilters;\n\t\tconst allSupported = supported != null && options.filters.every(f => supported.includes(f));\n\t\tif (!allSupported) {\n\t\t\twarnings.push(\n\t\t\t\tunsupportedWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t`One or more requested issue filters are not supported by '${options.providerId}'.`,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn emptyPage(true);\n\t\t}\n\t}\n\n\t// `includeAllAssignees` drops the user scope, but a user-relative filter (Author/Mention) is meaningless\n\t// without a user. Passing both to the provider degrades silently: Jira, seeing no user, falls through to\n\t// an unscoped project fetch and returns EVERY issue instead of the requested author's/mentions. Reject\n\t// the incompatible combination up front rather than publishing a differently-scoped set as the result.\n\tif (options.includeAllAssignees === true && options.filters?.some(f => f !== IssueFilter.Assignee)) {\n\t\twarnings.push(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`\\`includeAllAssignees\\` cannot be combined with an author/mention filter for '${options.providerId}' (those filters require a user scope).`,\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// Scope to the current user's assigned issues unless the caller broadens to all assignees. Resolve the\n\t// handle from each resource's own account (multi-account safe), capturing any error so its kind\n\t// (e.g. auth) is preserved rather than collapsed to a generic warning.\n\tlet usersByResourceId: Map<string, string> | undefined;\n\t// The same accounts keyed by the same resource, holding the provider's stable id rather than the handle. A\n\t// separate map rather than a widened value because the two are resolved and consumed independently: a\n\t// provider may carry an account with no id, and `scopedProjectsWithUsers` gates on the HANDLE alone, so\n\t// folding them together would drop a resource whose id happened to be missing.\n\tlet userIdsByResourceId: Map<string, string> | undefined;\n\tlet accountLookupFailed = false;\n\tif (options.includeAllAssignees !== true) {\n\t\tusersByResourceId = new Map<string, string>();\n\t\tuserIdsByResourceId = new Map<string, string>();\n\t\tconst accounts = await mapBounded(scopedResources, providerFanOutConcurrency, async resource => ({\n\t\t\tresource: resource,\n\t\t\t...(await runCaptured(options.providerId, domain, options.connectionId, () =>\n\t\t\t\tintegration.getAccountForResourceResult(resource, options.connectionId),\n\t\t\t)),\n\t\t}));\n\n\t\tfor (const { resource, value: account, warning: accountWarning } of accounts) {\n\t\t\tconst user = account?.username ?? account?.name ?? undefined;\n\t\t\tif (user != null) {\n\t\t\t\tconst resourceKey = resourceIdForProject(resource) ?? resource.key;\n\t\t\t\tusersByResourceId.set(resourceKey, user);\n\t\t\t\t// Empty-checked, not just null-checked: an account shape declares `id` as a plain string, and a\n\t\t\t\t// provider with nothing to put there sets `''` rather than omitting it — which would otherwise\n\t\t\t\t// reach a user-field query as an empty identity and match nothing.\n\t\t\t\tif (account?.id != null && account.id !== '') {\n\t\t\t\t\tuserIdsByResourceId.set(resourceKey, account.id);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Deduped: a captured `accountWarning` carries only the provider error, with no resource in its\n\t\t\t// message, so one expired token across N resources would otherwise repeat verbatim N times. The\n\t\t\t// synthesized fallback names the resource, so those stay distinct on their own.\n\t\t\tappendDedupedWarning(\n\t\t\t\twarnings,\n\t\t\t\taccountWarning ??\n\t\t\t\t\totherWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Could not resolve the current user for '${resourceLabel(resource)}'; skipping that resource to avoid returning issues assigned to others.`,\n\t\t\t\t\t),\n\t\t\t);\n\t\t\taccountLookupFailed = true;\n\t\t}\n\t}\n\n\tconst fallbackUserForUnscopedProject =\n\t\tscopedResources.length === 1 && usersByResourceId?.size === 1\n\t\t\t? usersByResourceId.values().next().value\n\t\t\t: undefined;\n\tconst userForProject = (project: ResourceDescriptor): string | undefined => {\n\t\tconst resourceId = resourceIdForProject(project);\n\t\tif (resourceId != null) {\n\t\t\tconst user = usersByResourceId?.get(resourceId);\n\t\t\tif (user != null) {\n\t\t\t\treturn user;\n\t\t\t}\n\t\t}\n\n\t\t// Some providers/tests return project descriptors without their parent resource id. When we have only\n\t\t// one scoped resource, re-use that sole resolved user rather than silently dropping every project.\n\t\treturn fallbackUserForUnscopedProject;\n\t};\n\t// Deliberately NOT falling back the way `userForProject` does. That fallback exists so a project with no\n\t// parent resource id still gets scoped rather than dropped, and the handle is what the drop is judged on;\n\t// the id is optional everywhere it is read, so a miss degrades to the handle instead of widening the read.\n\tconst userIdForProject = (project: ResourceDescriptor): string | undefined => {\n\t\tconst resourceId = resourceIdForProject(project);\n\t\treturn resourceId != null ? userIdsByResourceId?.get(resourceId) : undefined;\n\t};\n\n\tconst retryProjectKeys = new Set<string>();\n\tconst completedProjectKeys = new Set(priorCompletedProjects);\n\tfor (const retryProject of priorRetryProjects) {\n\t\tcompletedProjectKeys.delete(retryProject);\n\t}\n\tconst scopedProjectsWithUsers =\n\t\tusersByResourceId != null\n\t\t\t? matchedProjects.filter(project => {\n\t\t\t\t\tif (userForProject(project) != null) return true;\n\n\t\t\t\t\tconst key = projectKey(project);\n\t\t\t\t\tretryProjectKeys.add(key);\n\t\t\t\t\tcompletedProjectKeys.delete(key);\n\t\t\t\t\treturn false;\n\t\t\t\t})\n\t\t\t: matchedProjects;\n\n\t// Select the untouched window plus any earlier windows/projects that explicitly need retrying. Keys\n\t// include the parent resource so two trackers can reuse the same project id without collapsing.\n\tconst projectsByRetryKey = new Map(scopedProjectsWithUsers.map(project => [projectKey(project), project]));\n\tconst scopedProjectsByKey = new Map<string, ResourceDescriptor>();\n\tconst addScopedProject = (project: ResourceDescriptor): void => {\n\t\tconst key = projectKey(project);\n\t\tif (completedProjectKeys.has(key)) return;\n\n\t\tscopedProjectsByKey.set(key, project);\n\t};\n\tfor (const retryProject of priorRetryProjects) {\n\t\tconst project = projectsByRetryKey.get(retryProject);\n\t\tif (project != null) {\n\t\t\taddScopedProject(project);\n\t\t} else if (projectDiscoveryFailed || projectDiscoveryTruncated) {\n\t\t\tretryProjectKeys.add(retryProject);\n\t\t}\n\t}\n\tconst windowPages = new Set<number>(priorRetryPages);\n\tif (normalWindowPage != null) {\n\t\twindowPages.add(normalWindowPage);\n\t}\n\tif (!paginated && (projectDiscoveryFailed || projectDiscoveryTruncated)) {\n\t\twindowPages.add(1);\n\t}\n\tif (paginated) {\n\t\tfor (const windowPage of windowPages) {\n\t\t\tconst windowStart = (windowPage - 1) * projectsPerPage;\n\t\t\tfor (const project of scopedProjectsWithUsers.slice(windowStart, windowStart + projectsPerPage)) {\n\t\t\t\taddScopedProject(project);\n\t\t\t}\n\t\t}\n\t} else if (compositeCursor == null || priorRetryPages.length > 0) {\n\t\t// An unpaged retry-only cursor should re-read only the explicitly failed projects already added\n\t\t// above. Discovery retries still rescan the aggregate set so newly recovered projects are included.\n\t\tfor (const project of scopedProjectsWithUsers) {\n\t\t\taddScopedProject(project);\n\t\t}\n\t}\n\tconst scopedProjects = [...scopedProjectsByKey.values()];\n\tconst furthestWindowPage = windowPages.size > 0 ? Math.max(...windowPages) : normalWindowPage;\n\tconst normalWindowEnd =\n\t\tfurthestWindowPage != null ? furthestWindowPage * projectsPerPage : scopedProjectsWithUsers.length;\n\tconst nextPage =\n\t\tpaginated && furthestWindowPage != null && scopedProjectsWithUsers.length > normalWindowEnd\n\t\t\t? furthestWindowPage + 1\n\t\t\t: undefined;\n\tconst retryWindowPages = (): number[] => {\n\t\tif (!projectDiscoveryFailed && !projectDiscoveryTruncated && retryProjectKeys.size === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst pages = new Set(windowPages);\n\t\tif (pages.size === 0) {\n\t\t\t// A terminal retry has no untouched next window to identify its origin. Keep a manual window\n\t\t\t// marker so a later partial discovery can reconcile against the projects already emitted.\n\t\t\tpages.add(paginated ? Math.max(1, page - 1) : 1);\n\t\t}\n\t\treturn [...pages];\n\t};\n\tif (scopedProjects.length === 0) {\n\t\t// The discovered projects didn't intersect the requested filter/window, or every matching resource\n\t\t// failed user resolution. If discovery or account lookup was partial, the empty result is not a\n\t\t// proven-empty account — carry `fetchFailed` so the caller knows issues may be missing.\n\t\treturn emptyPage(projectDiscoveryFailed || accountLookupFailed, projectDiscoveryTruncated, {\n\t\t\tnextPage: nextPage,\n\t\t\tpages: retryWindowPages(),\n\t\t\tprojects: [...retryProjectKeys],\n\t\t\tcompletedProjects: [...completedProjectKeys],\n\t\t});\n\t}\n\n\t// A read spanning several projects is a merge, so it can only honor a key a normalized issue carries. Bound here\n\t// rather than next to the key's validation above, because the project count is only known now — while still\n\t// before any project read, so a refusal costs no request. Refused rather than served as concatenated\n\t// per-project runs, which would look ordered without being so.\n\t//\n\t// Counted over every project the READ covers, not the `scopedProjects` window this page happens to hold: the\n\t// window is `itemsPerPage` projects wide, so counting it would refuse a 20-project page and then serve the\n\t// final 1-project page of the same paginated call — the same key answered two ways, with the earlier pages'\n\t// issues missing from a read that reported success.\n\t//\n\t// Projects are not the only thing a tracker read merges: a user-scoped read asking for several relationships\n\t// issues one provider query PER relationship and unions them within each project (Jira runs one JQL drain per\n\t// filter and folds them into one map, in filter order), so a single-project page of two filters is just as\n\t// concatenated as a two-project one. Counting only projects published those runs under the requested key.\n\t// Not gated on the provider: the two trackers that don't fan out (Linear, Trello) declare `Assignee` as their\n\t// only supported filter, so a multi-filter read is already refused above and can never reach this.\n\tconst mergesFilters = options.includeAllAssignees !== true && (options.filters?.length ?? 0) > 1;\n\tconst ordering = toIssueOrdering(sort, scopedProjectsWithUsers.length > 1 || mergesFilters);\n\tif (ordering.unmergeable != null) {\n\t\twarnings.push(\n\t\t\tunmergeableIssueSortWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tordering.unmergeable,\n\t\t\t\tscopedProjectsWithUsers.length > 1 ? 'projects' : 'filters',\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// One call for the whole window, so a tracker that can search several projects at once (Jira) does, instead\n\t// of paying one drain per project. Results still come back per project, which the accounting below needs.\n\tlet projectReads: IntegrationResult<ProjectIssuesDrain | undefined>[];\n\ttry {\n\t\tprojectReads = await integration.getIssuesForProjectsWithTruncationResult(\n\t\t\tscopedProjects.map(project => ({\n\t\t\t\tproject: project,\n\t\t\t\toptions: {\n\t\t\t\t\tuser: userForProject(project),\n\t\t\t\t\tuserId: userIdForProject(project),\n\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\tsort: sort,\n\t\t\t\t\tstate: options.state,\n\t\t\t\t},\n\t\t\t})),\n\t\t\toptions.connectionId,\n\t\t);\n\t} catch (ex) {\n\t\tconst error = toError(ex);\n\t\tprojectReads = scopedProjects.map(() => ({ error: error }));\n\t}\n\tconst perProject = await Promise.all(\n\t\tscopedProjects.map(async (project, index) => ({\n\t\t\tproject: project,\n\t\t\t...(await runCaptured(options.providerId, domain, options.connectionId, () =>\n\t\t\t\tPromise.resolve(projectReads[index]),\n\t\t\t)),\n\t\t})),\n\t);\n\n\t// Partial project discovery means some projects' issues are missing from this page; propagate it so the\n\t// page reports fetchFailed even when every discovered project's own read succeeded.\n\tlet fetchFailed = projectDiscoveryFailed || accountLookupFailed;\n\tlet truncationRecovery: 'narrow-scope' | 'none' | undefined = projectDiscoveryTruncated ? 'none' : undefined;\n\tlet drainMetadata: CollectionMetadata | undefined;\n\tfor (const { project, value: result, warning } of perProject) {\n\t\tconst key = projectKey(project);\n\t\tconst retryProject = result == null;\n\t\tif (warning != null) {\n\t\t\t// Deduped: the per-project warning is built from the provider error alone and names no project, so a\n\t\t\t// single failing token repeats verbatim once per project in the window.\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t\tfetchFailed = true;\n\t\t\ttruncationRecovery = 'none';\n\t\t}\n\t\t// A thrown/unsupported read (e.g. Linear not-implemented) surfaces as a warning with no value;\n\t\t// mark the aggregate as fetchFailed so an empty result isn't mistaken for \"no issues\".\n\t\tif (result == null) {\n\t\t\tfetchFailed = true;\n\t\t}\n\t\tif (result != null) {\n\t\t\titems.push(...result.values);\n\t\t\tif (result.truncated) {\n\t\t\t\tconst recovery = result.recovery;\n\t\t\t\ttruncationRecovery = truncationRecovery == null || truncationRecovery === recovery ? recovery : 'none';\n\t\t\t}\n\t\t\tif (result.metadata != null) {\n\t\t\t\tdrainMetadata = mergeCollectionMetadata(drainMetadata, result.metadata);\n\t\t\t}\n\t\t}\n\t\tif (retryProject) {\n\t\t\tretryProjectKeys.add(key);\n\t\t\tcompletedProjectKeys.delete(key);\n\t\t} else {\n\t\t\t// A usable partial/truncated project is emitted once and remains structurally incomplete.\n\t\t\t// Re-running the same project cursor would normally return the same prefix and duplicate it\n\t\t\t// across facade pages; only a project that returned no value is safe to retry automatically.\n\t\t\tcompletedProjectKeys.add(key);\n\t\t}\n\t}\n\n\t// Each provider query arrived ordered by the tracker; the union of them — across projects, and across the\n\t// per-relationship queries a multi-filter read fans out into — is not, and the union is what this read\n\t// publishes. A no-op when nothing merged, since the tracker already ordered that single run.\n\tconst orderedItems = ordering.order(items);\n\n\tconst projectReadReportedTruncation = truncationRecovery != null;\n\tconst drainAssessment = assessCollectionMetadata(options.providerId, domain, options.connectionId, drainMetadata);\n\tfor (const warning of drainAssessment.warnings) {\n\t\t// A project result already supplies the recovery semantics. Replace metadata's unstructured fallback with\n\t\t// the structured warning below, while retaining specific failures and omissions.\n\t\tif (projectReadReportedTruncation && !drainAssessment.fetchFailed && warning.omission == null) continue;\n\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\tfetchFailed = fetchFailed || drainAssessment.fetchFailed;\n\tif (drainAssessment.truncated) {\n\t\ttruncationRecovery = 'none';\n\t}\n\n\tconst projectTruncated = truncationRecovery != null;\n\tconst scopeTooLarge = !fetchFailed && truncationRecovery === 'narrow-scope';\n\tif (scopeTooLarge || (!fetchFailed && projectTruncated && !warnings.some(warning => warning.omission != null))) {\n\t\tappendDedupedWarning(\n\t\t\twarnings,\n\t\t\tincompleteReadWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tscopeTooLarge\n\t\t\t\t\t? 'Some projects hold more issues than one read can return; narrow the scope to read the rest.'\n\t\t\t\t\t: 'Some issues were omitted; the provider returned an incomplete result.',\n\t\t\t\tscopeTooLarge ? 'scope-too-large' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier project asserts the read succeeded; a later one may since have failed.\n\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\n\tconst retryPages = retryWindowPages();\n\tconst cursor = toIssueTrackerPageCursor({\n\t\tcurrentPage: page + 1,\n\t\tunpaged: !paginated,\n\t\tnextPage: nextPage,\n\t\tretryPages: retryPages,\n\t\tretryProjects: [...retryProjectKeys],\n\t\tcompletedProjects:\n\t\t\t(trackCompletedProjects ||\n\t\t\t\tprojectDiscoveryFailed ||\n\t\t\t\tprojectDiscoveryTruncated ||\n\t\t\t\tretryProjectKeys.size > 0) &&\n\t\t\t(retryPages.length > 0 || nextPage != null)\n\t\t\t\t? [...completedProjectKeys]\n\t\t\t\t: [],\n\t\tstate: options.state,\n\t});\n\treturn {\n\t\titems: orderedItems,\n\t\twarnings: warnings,\n\t\tpage: { currentPage: page, itemsPerPage: orderedItems.length, truncated: projectTruncated || undefined },\n\t\t// Failed-project retries alone are manual. Only an untouched project window is automatic progress.\n\t\thasMore: nextPage != null && cursor != null,\n\t\tcursor: cursor,\n\t\tfetchFailed: fetchFailed || undefined,\n\t};\n}\n"],"mappings":"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"}
@@ -2,10 +2,10 @@ import { getScopedLogger, maybeStartScopedLogger } from "../../logger.scoped.js"
2
2
  import { normalizePath } from "../../path.js";
3
3
  import { isWindows } from "./platform.js";
4
4
  import { join } from "path";
5
- import { stat } from "fs/promises";
6
- import * as process from "process";
7
5
  import { exec, execFile, spawn } from "child_process";
8
6
  import { access, constants } from "fs";
7
+ import { stat } from "fs/promises";
8
+ import * as process from "process";
9
9
  //#region ../utils/src/env/node/exec.ts
10
10
  var RunError = class extends Error {
11
11
  cmd;
@@ -1,5 +1,5 @@
1
- import { Deferred } from "./promise.js";
2
1
  import { UnifiedDisposable } from "./disposable.js";
2
+ import { Deferred } from "./promise.js";
3
3
  //#region ../utils/src/event.d.ts
4
4
  export interface Event<T> {
5
5
  (listener: (e: T) => any, thisArgs?: unknown, disposables?: {
@@ -132,7 +132,7 @@ it with `page` + `hasMore` + `cursor?`. **No read throws for a provider-side fai
132
132
  | `sweepClosedPullRequests` | `ProviderSweepResult` | Same, pinned to `['closed','merged']`. |
133
133
  | `broadenIssues` | `ProviderBroadenResult` | Per-org fan-out for every visible issue, unfiltered by assignee. |
134
134
  | `resolveRepository` | `ResolveRepositoryResult` | Remote URL → canonical provider identity (the `gk repo resolve` equivalent). |
135
- | `getCurrentAccount` | `CurrentAccountResult` | Who a git host connection is signed in as; trackers refuse. |
135
+ | `getCurrentAccount` | `CurrentAccountResult` | Who a connection is signed in as; Jira Data Center refuses. |
136
136
  | `getSupportedFilters` | filter capability table | Static, connection-free. See §7. |
137
137
 
138
138
  A provider that cannot serve a surface says so explicitly — a `kind: 'unsupported'` warning (§6) plus
@@ -263,9 +263,9 @@ fields on GitHub/GHE and Bitbucket DC and the by-id read's on Bitbucket Cloud. B
263
263
  `headOwner` naming another owner, since neither can find a fork by its owner. It is uncached and bypasses
264
264
  `IntegrationCacheProvider.getPullRequestForBranch`.
265
265
 
266
- `getCurrentAccount` answers who a git host connection is signed in as. It returns a single `account?` rather
267
- than `items`, and `account` is never absent without a warning: no session, a failed request, or an issue tracker,
268
- which has only a per-resource account and refuses (see §8). It goes through the host-supplied
266
+ `getCurrentAccount` answers who a connection is signed in as. It returns a single `account?` rather than
267
+ `items`, and `account` is never absent without a warning: no session, a failed request, or a provider that refuses
268
+ (see §8). Jira Cloud answers with the first site's account, since an Atlassian account id is the same on every site. It goes through the host-supplied
269
269
  `IntegrationManagerCacheProvider.getCurrentAccount` cache rather than adding a second one.
270
270
 
271
271
  That account is not always how a pull request names the same person. Azure DevOps Server gives one person a
@@ -1101,7 +1101,7 @@ footnote says otherwise.
1101
1101
  | `listIssueTrackerIssuesPage` | — | — | — | — | — | ✓ | ✓ | ✓ |
1102
1102
  | `broadenIssues` | ✓ | ✓ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ |
1103
1103
  | `resolveRepository` | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
1104
- | `getCurrentAccount` | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
1104
+ | `getCurrentAccount` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
1105
1105
 
1106
1106
  Bitbucket Data Center exposes its projects through `listOrgs`: both `id` and `name` are the project key.
1107
1107
  Pass that key as `org` to `listRepos` to select a project, or omit `org` to enumerate all accessible repositories,
@@ -1112,7 +1112,7 @@ Both reads deduplicate results across pages and retain the configured installati
1112
1112
  A link the server returns is kept only when it names that entry on the configured installation; otherwise the web and
1113
1113
  HTTPS links are rebuilt from the configured URL and an SSH link, whose host and port cannot be derived, is omitted.
1114
1114
 
1115
- Jira Data Center inherits Jira Cloud's row. Its reads are addressed per host, so `domain` selects the instance and
1115
+ Jira Data Center inherits Jira Cloud's row, except that it refuses `getCurrentAccount`. Its reads are addressed per host, so `domain` selects the instance and
1116
1116
  every read below is scoped to that one connection; a paged read that omits it gets the primary configured host,
1117
1117
  while `getIssuesBatch` refuses instead (see §4). `listOrgs` returns exactly one resource — the
1118
1118
  instance itself, synthesized from the configured host rather than fetched — and its projects carry the display name
@@ -266,11 +266,11 @@ deleted fork, so on such a branch one the first request didn't return can be mis
266
266
  same whichever read found it. Bitbucket DC and Azure DevOps serve base-repository branches only and refuse a
267
267
  `headOwner` naming another owner.
268
268
 
269
- **Current account resolution.** `getCurrentAccount` answers who a git host connection is signed in as, on
269
+ **Current account resolution.** `getCurrentAccount` answers who a connection is signed in as, on
270
270
  demand. Before it, Kepler learned the viewer's account only when its `IntegrationManagerCacheProvider`
271
271
  `getCurrentAccount` hook fired during some other read, so "is this pull request mine" could stay unanswered until
272
272
  something else resolved the account. The read goes through that same hook, so there is still one cache. `account`
273
- is never absent without a warning. Jira, Linear and Trello refuse: they have only a per-resource account.
273
+ is never absent without a warning. Jira Data Center refuses.
274
274
 
275
275
  **Kepler-side follow-up:** `ProviderScopeFilter` carries a single `repo?: string` today and needs the criteria
276
276
  set; the `provider-data` adapter then routes "All visible" to `searchIssuesPage` + `countIssues`.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gitkraken/core-gitlens",
3
3
  "description": "GitLens core — shared Git / AI / GitHub primitives for internal GitKraken consumption",
4
- "version": "0.10.6",
4
+ "version": "0.11.0",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": {
7
7
  "name": "GitKraken",