@gitkraken/integrations-tools 0.0.0-stage → 0.2.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.
- package/CHANGELOG.md +18 -0
- package/LICENSE +19 -0
- package/README.md +144 -2
- package/dist/errors.d.ts +33 -0
- package/dist/errors.js +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1 -0
- package/dist/internal/json.d.ts +8 -0
- package/dist/internal/json.js +1 -0
- package/dist/launchpad.d.ts +46 -0
- package/dist/launchpad.js +1 -0
- package/dist/models/launchpad.d.ts +27 -0
- package/dist/models/launchpad.js +1 -0
- package/dist/provider-tokens.d.ts +45 -0
- package/dist/provider-tokens.js +1 -0
- package/dist/providers.d.ts +19 -0
- package/dist/providers.js +1 -0
- package/dist/pull-requests/azure.d.ts +12 -0
- package/dist/pull-requests/azure.js +1 -0
- package/dist/pull-requests/bitbucket-server.d.ts +11 -0
- package/dist/pull-requests/bitbucket-server.js +1 -0
- package/dist/pull-requests/bitbucket.d.ts +10 -0
- package/dist/pull-requests/bitbucket.js +1 -0
- package/dist/pull-requests/github.d.ts +7 -0
- package/dist/pull-requests/github.js +1 -0
- package/dist/pull-requests/gitlab.d.ts +10 -0
- package/dist/pull-requests/gitlab.js +1 -0
- package/dist/pull-requests/shared.d.ts +83 -0
- package/dist/pull-requests/shared.js +1 -0
- package/dist/pull-requests.d.ts +27 -0
- package/dist/pull-requests.js +1 -0
- package/dist/remotes.d.ts +37 -0
- package/dist/remotes.js +1 -0
- package/package.json +33 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/) and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.2.0] - 2026-10-26
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- First publish of `@gitkraken/integrations-tools` to npm, under the Core Tools License (the same `LICENSE` as `@gitkraken/account-tools`). It depends on `@gitkraken/api-tools`.
|
|
14
|
+
- Git-hosting provider ids and remote resolution (gkcli#1736): `GitHostingProviderId` (`github`, `githubEnterprise`, `gitlab`, `gitlabSelfHosted`, `bitbucket`, `bitbucketServer`, `azure`, `azureDevopsServer`), `normalizeProviderId`, `providerDisplayName`, `isSelfHosted`, and `parseRemoteUrl(remoteUrl, selfHosted?)`, which resolves a git remote to `{provider, owner, name, azureProject?, host}`. Connected self-hosted servers match first, then the cloud hosts, both by exact hostname. GitLab subgroups resolve to the full namespace, Bitbucket Server project keys are uppercased (`~user` kept), and Azure DevOps handles `dev.azure.com`, `*.visualstudio.com` (with or without `DefaultCollection`), the `v3/` ssh forms, and Azure DevOps Server collections. No dependencies.
|
|
15
|
+
- Provider tokens and `IntegrationError` (gkcli#1736): `getProviderToken(config, provider, signal?)` (`GET v1/provider-tokens/{id}`, returning a `ProviderToken` with `isPat`, `domain`, `tokenId`, ...) and `refreshProviderToken(config, provider, accessToken, signal?)` (`POST v1/provider-tokens/{id}/refresh`), over `@gitkraken/api-tools`' `Transport`. Neither caches or persists anything. Failures throw `IntegrationError` with a stable `code` (`not_logged_in`, `not_connected`, `unsupported`, `provider_auth`, `rate_limited`, `invalid_request`, `provider_error`), `status`, `retryable` and `provider`; a GitKraken HTTP 429 is `rate_limited`, and an error from an aborted `signal` is rethrown unchanged. `IntegrationsClientConfig` is structurally satisfied by account-tools' `AccountClientConfig`.
|
|
16
|
+
- Pull-request creation for GitHub, GitHub Enterprise, GitLab and self-hosted GitLab (gkcli#1736): `createPullRequest(input, {token, request?, logger?, signal?})` returns `{provider, owner, repo, id, number, url, draft}` and `supportsDraft(provider)` reports whether the provider can open a draft (all but Bitbucket Server). GitHub sends `draft`; GitLab has no create-time flag, so `draft: true` prefixes the title with `Draft: ` (unless it already carries a draft marker) and always opens the merge request with "delete source branch on merge" set (GitHub has no equivalent). Provider failures map to `IntegrationError`: 401 and 403 are `provider_auth`, 429 and a GitHub 403 with an exhausted `x-ratelimit-remaining` or a secondary limit (`retry-after`) are a retryable `rate_limited`, other 4xx are `invalid_request` with the provider's message, 5xx and network failures are a retryable `provider_error`, and a 2xx whose body has no id or url or is not valid JSON is a non-retryable `provider_error` (the pull request may have been created). Nothing is pushed, assigned, retried or refreshed. A token that belongs to another provider is rejected before any request.
|
|
17
|
+
- `createPullRequest` also opens pull requests on Bitbucket Cloud, Bitbucket Server and Azure DevOps (cloud and Server) (gkcli#1736). Bitbucket and Azure send `draft`/`isDraft` natively; a Bitbucket Cloud app password needs the account `username` on the token (otherwise `invalid_request`, nothing sent); Azure DevOps Server needs its collection URL as the connection domain. Bitbucket Server has no draft state, so `draft: true` throws `unsupported` before any request (`supportsDraft('bitbucketServer')` is `false`). Azure's 203 sign-in page maps to `provider_auth`.
|
|
18
|
+
- Initial scaffold of `@gitkraken/integrations-tools` (gkcli#1726 P5a): the launchpad priority model tables (`launchpadActionCategories`, `launchpadGroups`, category/group maps, action label/detail text) ported from GitLens' `src/plus/launchpad/models/launchpad.ts`, and the pure grouping, sorting and suggested-actions functions ported from `src/plus/launchpad/launchpadProvider.ts` (`getLaunchpadItemGroups`, `groupAndSortLaunchpadItems`, `countLaunchpadItemGroups`, `sortLaunchpadItems`, `getSuggestedActions`). `@vscode/l10n` is replaced with plain English strings and `@gitlens/utils` with local helpers. Private; license pending the #1726 B1 decision (see root `ARCHITECTURE.md`). The provider abstraction and `@gitkraken/provider-apis` clients are **not** included (P5b, blocked). `sortLaunchpadItems` is stable (ties on pinned/category/updated date keep input order), and the maps returned by `groupAndSortLaunchpadItems`/`countLaunchpadItemGroups` omit the never-emitted `current-branch` key.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Core Tools License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Axosoft, LLC dba GitKraken ("GitKraken")
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") may be compiled as part of GitKraken software products, including the gitkraken/vscode-gitlens open source project (collectively, the "GitKraken Products"), to the extent the Software is a required component of the GitKraken Products; provided, however, that the Software and its functionality may only be used if you (and any entity that you represent) have agreed to, and are in compliance with, the GitKraken End User License Agreement, available at https://gitkraken.com/eula (the "EULA"), or other agreement governing the use of the Software, as agreed by you and GitKraken, and otherwise have a valid subscription for the correct number of user seats for the applicable version of the Software (e.g., GitLens Free+, GitLens Pro, GitLens Teams, and GitLens Enterprise).
|
|
6
|
+
|
|
7
|
+
The Software is licensed, not sold. This license only gives you some rights to use the Software. GitKraken reserves all other rights. Unless applicable law gives you more rights despite this limitation, you may use the Software only as expressly permitted in this license. In doing so, you must comply with any technical limitations in the Software that only allow you to use it in certain ways. You may not (i) work around any technical limitations in the Software, (ii) reverse engineer, decompile or disassemble the Software, or otherwise attempt to derive the source code for the Software, except and to the extent required by third party licensing terms governing use of certain open source components that may be included with the Software, (iii) remove, minimize, block or modify any notices of GitKraken or its suppliers in the Software, (iv) use the software in any way that is against the law, (v) host, share, publish, rent or lease the Software; (vi) distribute the Software as a stand-alone or integrated offering or combine it with any of your applications for others to use, (vii) use the Software other than in connection with validly licensed GitKraken Products or (viii) use any portion of the Software to create software with the same or similar functionality.
|
|
8
|
+
|
|
9
|
+
You agree that GitKraken and/or its licensors (as applicable) retain all right, title and interest in and to the Software and all modifications and/or patches thereto. You are not granted any other rights beyond what is expressly stated herein. Except as set forth above, it is forbidden to copy, merge, publish, distribute, sublicense, modify and/or sell the Software.
|
|
10
|
+
|
|
11
|
+
If you give feedback about the Software to GitKraken, you give to GitKraken, without charge, the right to use, share and commercialize your feedback in any way and for any purpose. You will not give feedback that is subject to a license that requires GitKraken to license its software or documentation to third parties because we include your feedback in them. These rights survive this License.
|
|
12
|
+
|
|
13
|
+
The full text of this Core Tools License shall be included in all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
This License and the EULA, if applicable, are the entire agreement for the Software. You must comply with all domestic and international export laws and regulations that apply to the Software, which include restrictions on destinations, end users and end use. This License is governed and construed by the laws of the State of Arizona, United States without regard to any choice or conflict of law principles or rules.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL GITKRAKEN, THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
18
|
+
|
|
19
|
+
For all third party components incorporated into the Software, those components are licensed under the original license provided by the owner of the applicable component.
|
package/README.md
CHANGED
|
@@ -1,3 +1,145 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @gitkraken/integrations-tools
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Runtime-agnostic GitKraken integration primitives:
|
|
4
|
+
|
|
5
|
+
- **Remote resolution**: `parseRemoteUrl` turns a git remote URL into the provider, owner and repository the provider APIs need.
|
|
6
|
+
- **Provider tokens**: `getProviderToken` / `refreshProviderToken` fetch and rotate a git provider connection's token from the GitKraken API.
|
|
7
|
+
- **Pull-request creation**: `createPullRequest` opens a pull request (a merge request on GitLab) on GitHub, GitHub Enterprise, GitLab, self-hosted GitLab, Bitbucket, Bitbucket Server, Azure DevOps and Azure DevOps Server.
|
|
8
|
+
- **Launchpad prioritization**: the priority-model tables and pure grouping/sorting/suggested-actions functions, ported from GitLens' `src/plus/launchpad/` (`origin/main`).
|
|
9
|
+
|
|
10
|
+
It does **not** include the GitLens provider abstraction (`packages/plus/integrations`) or `@gitkraken/provider-apis` clients; moving those is gkcli#1726 P5b, which is blocked on owner decisions (see root `ARCHITECTURE.md`).
|
|
11
|
+
|
|
12
|
+
The package never owns the network: every call takes (or accepts) an injected `ApiRequest` from `@gitkraken/api-tools`, and defaults to a `fetch` requester. Nothing is cached or persisted, and nothing is pushed.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add @gitkraken/integrations-tools
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Open a pull request
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import {
|
|
24
|
+
createPullRequest,
|
|
25
|
+
getProviderToken,
|
|
26
|
+
IntegrationError,
|
|
27
|
+
parseRemoteUrl,
|
|
28
|
+
} from '@gitkraken/integrations-tools';
|
|
29
|
+
|
|
30
|
+
const wantDraft = false;
|
|
31
|
+
|
|
32
|
+
// `selfHosted` lists the servers the user connected to GitKraken: [{ provider, url }].
|
|
33
|
+
const remote = parseRemoteUrl('git@gitlab.com:group/sub/repo.git', selfHosted);
|
|
34
|
+
// -> { provider: 'gitlab', owner: 'group/sub', name: 'repo', host: 'gitlab.com' }
|
|
35
|
+
if (remote == null) throw new Error('unknown git provider');
|
|
36
|
+
|
|
37
|
+
const config = { apiBaseUrl: 'https://api.gitkraken.com/', getToken: async () => gitkrakenToken };
|
|
38
|
+
const token = await getProviderToken(config, remote.provider);
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const pr = await createPullRequest(
|
|
42
|
+
{
|
|
43
|
+
provider: remote.provider,
|
|
44
|
+
owner: remote.owner,
|
|
45
|
+
repo: remote.name,
|
|
46
|
+
azureProject: remote.azureProject,
|
|
47
|
+
base: 'main',
|
|
48
|
+
head: 'my-branch',
|
|
49
|
+
title: 'Add the thing',
|
|
50
|
+
body: 'Why the thing is needed.',
|
|
51
|
+
draft: wantDraft, // check supportsDraft(remote.provider) first: Bitbucket Server throws `unsupported`
|
|
52
|
+
},
|
|
53
|
+
{ token },
|
|
54
|
+
);
|
|
55
|
+
console.log(pr.url);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error instanceof IntegrationError) console.error(error.code, error.message, error.retryable);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`config` is structurally satisfied by account-tools' `AccountClientConfig`.
|
|
62
|
+
|
|
63
|
+
### `parseRemoteUrl(remoteUrl, selfHosted?)`
|
|
64
|
+
|
|
65
|
+
Returns `{ provider, owner, name, azureProject?, host }`, or `undefined`.
|
|
66
|
+
|
|
67
|
+
- Matching is by exact hostname, never by substring (`notgithub.example.com` is not GitHub). Connected self-hosted servers are checked first, in array order (an entry may carry a path such as `https://host/gitlab`, or an Azure DevOps Server collection such as `https://host/tfs/DefaultCollection`, which is required for that provider); then the cloud hosts `github.com`, `gitlab.com`, `bitbucket.org`/`bitbucket.com`, and `dev.azure.com`/`ssh.dev.azure.com`/`*.visualstudio.com`.
|
|
68
|
+
- `https`, `http`, `ssh` and `git` URLs and the scp form (`git@host:path`) are supported; userinfo and ports are ignored.
|
|
69
|
+
- Layouts: GitLab `owner` is the full namespace (`group/sub`); Bitbucket Server `owner` is the project key, uppercased unless it is a `~user` personal project, and a leading `scm/` is dropped; Azure DevOps `owner` is the organization (cloud) or collection name (Server) and `azureProject` the project (a repository named after its project may omit it in the URL).
|
|
70
|
+
|
|
71
|
+
### `getProviderToken` and `refreshProviderToken`
|
|
72
|
+
|
|
73
|
+
`getProviderToken(config, provider, signal?)` calls `GET v1/provider-tokens/{id}` and returns a `ProviderToken` (`accessToken`, `type`, `isPat`, and `domain`/`tokenId`/`isPrimary`/`expiresIn` when present). For a Bitbucket Cloud app password the host sets `username` on the token itself; the cloud does not return it. `refreshProviderToken(config, provider, accessToken, signal?)` rotates an expiring OAuth token and returns the new access token (not for PATs or self-hosted servers). An error thrown after the caller's `signal` aborted is rethrown unchanged. Callers decide when to refresh: on a `provider_auth` error from `createPullRequest`, refresh once and retry.
|
|
74
|
+
|
|
75
|
+
### `createPullRequest(input, { token, request?, logger?, signal? })`
|
|
76
|
+
|
|
77
|
+
| Provider | Draft |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `github`, `githubEnterprise` | `draft` field |
|
|
80
|
+
| `gitlab`, `gitlabSelfHosted` | no create-time flag: the title is prefixed with `Draft: ` (unless it already starts with `Draft:`, `[Draft]` or `(Draft)`) |
|
|
81
|
+
| `bitbucket` | `draft` field |
|
|
82
|
+
| `bitbucketServer` | unsupported: `draft: true` throws `unsupported` and sends nothing (`supportsDraft` is `false`) |
|
|
83
|
+
| `azure`, `azureDevopsServer` | `isDraft` field |
|
|
84
|
+
|
|
85
|
+
The self-hosted providers take their server URL from `token.domain`; without one the call throws `invalid_request` and sends nothing. Azure DevOps Server needs its collection URL there. A Bitbucket Cloud app password needs `token.username` (the Bitbucket username, not the email). A token for a different provider than `input.provider` is rejected, so a token is never sent to the wrong host.
|
|
86
|
+
|
|
87
|
+
Failures are `IntegrationError`s with a stable `code`, `status`, `retryable` and `provider`:
|
|
88
|
+
|
|
89
|
+
| `code` | When |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| `not_logged_in` | the GitKraken API rejected the caller's own token (token functions) |
|
|
92
|
+
| `not_connected` | the provider is not connected to the GitKraken account (token functions) |
|
|
93
|
+
| `unsupported` | the provider cannot do what was asked (a draft on Bitbucket Server) |
|
|
94
|
+
| `provider_auth` | the provider rejected the connection's token: HTTP 401, 403, or Azure's 203 sign-in page |
|
|
95
|
+
| `rate_limited` | HTTP 429, or a GitHub 403 with an exhausted `x-ratelimit-remaining` or a `retry-after` header (retryable) |
|
|
96
|
+
| `invalid_request` | any other 4xx, with the provider's message (for example "a pull request already exists"), or input refused before any request |
|
|
97
|
+
| `provider_error` | 5xx or a network failure (retryable), or a 2xx without the id or url or with an invalid body (not retryable: the pull request may have been created) |
|
|
98
|
+
|
|
99
|
+
## Launchpad
|
|
100
|
+
|
|
101
|
+
### What's here
|
|
102
|
+
|
|
103
|
+
- `models/launchpad.ts`: the priority tables — `launchpadActionCategories`,
|
|
104
|
+
`launchpadGroups`, `launchpadCategoryToGroupMap`,
|
|
105
|
+
`sharedCategoryToLaunchpadActionCategoryMap`, `prActionsMap`,
|
|
106
|
+
`launchpadPriorityGroups` (display priority order: `mergeable`, `blocked`,
|
|
107
|
+
`follow-up`, `needs-review`), and `launchpadGroupLabelMap`/`actionGroupMap`
|
|
108
|
+
(plain English strings; GitLens' VS Code icon map and `@vscode/l10n` calls
|
|
109
|
+
are dropped, not ported).
|
|
110
|
+
- `launchpad.ts`: `getLaunchpadItemGroups`, `groupAndSortLaunchpadItems`,
|
|
111
|
+
`countLaunchpadItemGroups`, `sortLaunchpadItems`, `getSuggestedActions` — ported
|
|
112
|
+
from `launchpadProvider.ts`, operating on the minimal `LaunchpadItemInput`
|
|
113
|
+
input type instead of GitLens' full `LaunchpadItem` (no `vscode`, no
|
|
114
|
+
`openRepository`/local-branch lookups). `groupAndSortLaunchpadItems`/`countLaunchpadItemGroups`
|
|
115
|
+
never contain a `current-branch` key; `sortLaunchpadItems` keeps input order for ties.
|
|
116
|
+
- `providers.ts`: `GitHostingProviderId` (the eight GitKraken cloud ids, e.g. `github`,
|
|
117
|
+
`gitlabSelfHosted`, `azureDevopsServer`), `normalizeProviderId` (storage keys, CLI
|
|
118
|
+
names and cloud ids to an id, or `undefined`), `providerDisplayName`, `isSelfHosted`.
|
|
119
|
+
- `remotes.ts`: `parseRemoteUrl` resolves a git remote to a `RemoteRepository`
|
|
120
|
+
(`{provider, owner, name, azureProject?, host}`); `SelfHostedProvider` describes a
|
|
121
|
+
connected self-hosted server (`{provider, url}`).
|
|
122
|
+
|
|
123
|
+
### Usage
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import {
|
|
127
|
+
getLaunchpadItemGroups,
|
|
128
|
+
getSuggestedActions,
|
|
129
|
+
type LaunchpadItemInput,
|
|
130
|
+
sortLaunchpadItems,
|
|
131
|
+
} from '@gitkraken/integrations-tools';
|
|
132
|
+
|
|
133
|
+
const items: LaunchpadItemInput[] = [...];
|
|
134
|
+
const aiReviewAllowed = hostAiEnabled; // org + user AI setting; required so a missing flag cannot fail open
|
|
135
|
+
sortLaunchpadItems(items);
|
|
136
|
+
for (const item of items) {
|
|
137
|
+
const groups = getLaunchpadItemGroups(item);
|
|
138
|
+
const actions = getSuggestedActions(item.actionableCategory, false, aiReviewAllowed);
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`sortLaunchpadItems` reorders and returns the same array; `groupAndSortLaunchpadItems`
|
|
143
|
+
also sorts its input array in place. An unsnoozed draft item in the `needs-my-review`
|
|
144
|
+
category appears in both the `draft` and `needs-review` groups, but
|
|
145
|
+
`countLaunchpadItemGroups` counts it under `draft` and excludes it from `needs-review`.
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { GitHostingProviderId } from "./providers.js";
|
|
2
|
+
|
|
3
|
+
//#region src/errors.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Stable, machine-readable integration-error codes.
|
|
6
|
+
*
|
|
7
|
+
* - `not_logged_in`: the GitKraken API rejected the caller's own token (HTTP 401).
|
|
8
|
+
* - `not_connected`: the provider is not connected to the GitKraken account.
|
|
9
|
+
* - `unsupported`: the provider cannot do what was asked (e.g. a draft pull request on Bitbucket Server).
|
|
10
|
+
* - `provider_auth`: the git provider rejected the connection's token (HTTP 401 or 403).
|
|
11
|
+
* - `rate_limited`: the provider throttled the request (HTTP 429, or GitHub's exhausted rate limit). Retryable.
|
|
12
|
+
* - `invalid_request`: any other 4xx, or input the library refuses before sending a request.
|
|
13
|
+
* - `provider_error`: a 5xx, a network failure, or a response that is not the expected shape. Retryable.
|
|
14
|
+
*/
|
|
15
|
+
type IntegrationErrorCode = 'not_logged_in' | 'not_connected' | 'unsupported' | 'provider_auth' | 'rate_limited' | 'invalid_request' | 'provider_error';
|
|
16
|
+
interface IntegrationErrorOptions {
|
|
17
|
+
/** The HTTP status when the error came from a response. */
|
|
18
|
+
status?: number;
|
|
19
|
+
/** Whether repeating the same call later may succeed. Defaults to `true` for `rate_limited` and `provider_error`. */
|
|
20
|
+
retryable?: boolean;
|
|
21
|
+
provider?: GitHostingProviderId;
|
|
22
|
+
cause?: unknown;
|
|
23
|
+
}
|
|
24
|
+
/** A typed integrations failure. */
|
|
25
|
+
declare class IntegrationError extends Error {
|
|
26
|
+
readonly code: IntegrationErrorCode;
|
|
27
|
+
readonly status?: number;
|
|
28
|
+
readonly retryable: boolean;
|
|
29
|
+
readonly provider?: GitHostingProviderId;
|
|
30
|
+
constructor(code: IntegrationErrorCode, message: string, options?: IntegrationErrorOptions);
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { IntegrationError, IntegrationErrorCode, IntegrationErrorOptions };
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=class extends Error{code;status;retryable;provider;constructor(e,t,n){super(t,n?.cause===void 0?void 0:{cause:n.cause}),this.name=`IntegrationError`,this.code=e,this.status=n?.status,this.retryable=n?.retryable??(e===`rate_limited`||e===`provider_error`),this.provider=n?.provider}};export{e as IntegrationError};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { GitHostingProviderId, isSelfHosted, normalizeProviderId, providerDisplayName } from "./providers.js";
|
|
2
|
+
import { IntegrationError, IntegrationErrorCode, IntegrationErrorOptions } from "./errors.js";
|
|
3
|
+
import { LaunchpadAction, LaunchpadActionCategory, LaunchpadGroup, LaunchpadPriorityGroup, actionGroupMap, launchpadActionCategories, launchpadCategoryToGroupMap, launchpadGroupLabelMap, launchpadGroups, launchpadPriorityGroups, prActionsMap, sharedCategoryToLaunchpadActionCategoryMap } from "./models/launchpad.js";
|
|
4
|
+
import { LaunchpadItemInput, LaunchpadItemViewer, countLaunchpadItemGroups, getLaunchpadItemGroups, getSuggestedActions, groupAndSortLaunchpadItems, sortLaunchpadItems } from "./launchpad.js";
|
|
5
|
+
import { IntegrationsClientConfig, ProviderToken, getProviderToken, refreshProviderToken } from "./provider-tokens.js";
|
|
6
|
+
import { CreatePullRequestInput, CreatePullRequestOptions, CreatedPullRequest } from "./pull-requests/shared.js";
|
|
7
|
+
import { createPullRequest, supportsDraft } from "./pull-requests.js";
|
|
8
|
+
import { RemoteRepository, SelfHostedProvider, parseRemoteUrl } from "./remotes.js";
|
|
9
|
+
export { type CreatePullRequestInput, type CreatePullRequestOptions, type CreatedPullRequest, type GitHostingProviderId, IntegrationError, type IntegrationErrorCode, type IntegrationErrorOptions, type IntegrationsClientConfig, type LaunchpadAction, type LaunchpadActionCategory, type LaunchpadGroup, type LaunchpadItemInput, type LaunchpadItemViewer, type LaunchpadPriorityGroup, type ProviderToken, type RemoteRepository, type SelfHostedProvider, actionGroupMap, countLaunchpadItemGroups, createPullRequest, getLaunchpadItemGroups, getProviderToken, getSuggestedActions, groupAndSortLaunchpadItems, isSelfHosted, launchpadActionCategories, launchpadCategoryToGroupMap, launchpadGroupLabelMap, launchpadGroups, launchpadPriorityGroups, normalizeProviderId, parseRemoteUrl, prActionsMap, providerDisplayName, refreshProviderToken, sharedCategoryToLaunchpadActionCategoryMap, sortLaunchpadItems, supportsDraft };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{IntegrationError as e}from"./errors.js";import{actionGroupMap as t,launchpadActionCategories as n,launchpadCategoryToGroupMap as r,launchpadGroupLabelMap as i,launchpadGroups as a,launchpadPriorityGroups as o,prActionsMap as s,sharedCategoryToLaunchpadActionCategoryMap as c}from"./models/launchpad.js";import{countLaunchpadItemGroups as l,getLaunchpadItemGroups as u,getSuggestedActions as d,groupAndSortLaunchpadItems as f,sortLaunchpadItems as p}from"./launchpad.js";import{isSelfHosted as m,normalizeProviderId as h,providerDisplayName as g}from"./providers.js";import{getProviderToken as _,refreshProviderToken as v}from"./provider-tokens.js";import{createPullRequest as y,supportsDraft as b}from"./pull-requests.js";import{parseRemoteUrl as x}from"./remotes.js";export{e as IntegrationError,t as actionGroupMap,l as countLaunchpadItemGroups,y as createPullRequest,u as getLaunchpadItemGroups,_ as getProviderToken,d as getSuggestedActions,f as groupAndSortLaunchpadItems,m as isSelfHosted,n as launchpadActionCategories,r as launchpadCategoryToGroupMap,i as launchpadGroupLabelMap,a as launchpadGroups,o as launchpadPriorityGroups,h as normalizeProviderId,x as parseRemoteUrl,s as prActionsMap,g as providerDisplayName,v as refreshProviderToken,c as sharedCategoryToLaunchpadActionCategoryMap,p as sortLaunchpadItems,b as supportsDraft};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/internal/json.d.ts
|
|
2
|
+
type JsonRecord = Record<string, unknown>;
|
|
3
|
+
/** Narrows a parsed body to an object; a string body is parsed when it holds a JSON object. */
|
|
4
|
+
declare function asRecord(body: unknown): JsonRecord | undefined;
|
|
5
|
+
declare function asString(value: unknown): string | undefined;
|
|
6
|
+
declare function isAbortError(error: unknown): boolean;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { JsonRecord, asRecord, asString, isAbortError };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(t){if(typeof t==`string`)try{return e(JSON.parse(t))}catch{return}return typeof t==`object`&&t&&!Array.isArray(t)?t:void 0}function t(e){return typeof e==`string`&&e!==``?e:void 0}function n(e){return e instanceof Error&&e.name===`AbortError`}export{e as asRecord,t as asString,n as isAbortError};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { LaunchpadAction, LaunchpadActionCategory, LaunchpadGroup } from "./models/launchpad.js";
|
|
2
|
+
|
|
3
|
+
//#region src/launchpad.d.ts
|
|
4
|
+
interface LaunchpadItemViewer {
|
|
5
|
+
pinned: boolean;
|
|
6
|
+
snoozed: boolean;
|
|
7
|
+
}
|
|
8
|
+
/** Minimal input covering only the fields the grouping/sorting/suggested-actions functions read. */
|
|
9
|
+
interface LaunchpadItemInput {
|
|
10
|
+
actionableCategory: LaunchpadActionCategory;
|
|
11
|
+
isDraft: boolean;
|
|
12
|
+
updatedDate: Date;
|
|
13
|
+
viewer: LaunchpadItemViewer;
|
|
14
|
+
}
|
|
15
|
+
/** Ported 1:1 from `getLaunchpadItemGroups`. */
|
|
16
|
+
declare function getLaunchpadItemGroups(item: LaunchpadItemInput): LaunchpadGroup[];
|
|
17
|
+
/**
|
|
18
|
+
* Ported 1:1 from `sortLaunchpadItems`. Order: pinned first, then `launchpadActionCategories` order, then most
|
|
19
|
+
* recently updated. Items tied on all three keep their input order (`Array.prototype.sort` is stable), so hosts
|
|
20
|
+
* merging results from several providers should pre-sort by a stable key (e.g. provider + id) for deterministic
|
|
21
|
+
* output. Sorts `items` in place and returns it.
|
|
22
|
+
*/
|
|
23
|
+
declare function sortLaunchpadItems<T extends LaunchpadItemInput>(items: T[]): T[];
|
|
24
|
+
/**
|
|
25
|
+
* Ported 1:1 from `groupAndSortLaunchpadItems`, minus the `current-branch` group, which is
|
|
26
|
+
* omitted from the returned map (see module doc).
|
|
27
|
+
*
|
|
28
|
+
* Reorders `items` in place (via `sortLaunchpadItems`) before grouping.
|
|
29
|
+
*/
|
|
30
|
+
declare function groupAndSortLaunchpadItems<T extends LaunchpadItemInput>(items?: T[]): Map<LaunchpadGroup, T[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Ported 1:1 from `countLaunchpadItemGroups`, minus the `current-branch` group, which is
|
|
33
|
+
* omitted from the returned map (see module doc).
|
|
34
|
+
*
|
|
35
|
+
* Counts a draft item whose category maps to `needs-review` only under `draft` (inherited from GitLens), while
|
|
36
|
+
* `getLaunchpadItemGroups`/`groupAndSortLaunchpadItems` also list it under `needs-review`.
|
|
37
|
+
*/
|
|
38
|
+
declare function countLaunchpadItemGroups(items?: LaunchpadItemInput[]): Map<LaunchpadGroup, number>;
|
|
39
|
+
/**
|
|
40
|
+
* Ported from GitLens `getSuggestedActions`. Returns the category's base actions (`prActionsMap`), then
|
|
41
|
+
* `start-review` when `aiReviewAllowed`, then either `show-overview`/`open-changes`/`open-in-graph`
|
|
42
|
+
* (`isCurrentBranch`) or `open-worktree`/`switch`/`open-in-graph`.
|
|
43
|
+
*/
|
|
44
|
+
declare function getSuggestedActions(category: LaunchpadActionCategory, isCurrentBranch: boolean, aiReviewAllowed: boolean): LaunchpadAction[];
|
|
45
|
+
//#endregion
|
|
46
|
+
export { LaunchpadItemInput, LaunchpadItemViewer, countLaunchpadItemGroups, getLaunchpadItemGroups, getSuggestedActions, groupAndSortLaunchpadItems, sortLaunchpadItems };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{launchpadActionCategories as e,launchpadCategoryToGroupMap as t,launchpadGroups as n,prActionsMap as r}from"./models/launchpad.js";const i=n.filter(e=>e!==`current-branch`);function a(e,t){let n=e.get(t);if(n===void 0)throw Error(`missing map entry for ${String(t)}`);return n}function o(e){if(e.viewer.snoozed)return[`snoozed`];let n=[];e.viewer.pinned&&n.push(`pinned`),e.isDraft&&n.push(`draft`);let r=a(t,e.actionableCategory);return(!e.isDraft||r===`needs-review`)&&n.push(r),n}function s(t){return t.sort((t,n)=>(t.viewer.pinned?-1:1)-(n.viewer.pinned?-1:1)||e.indexOf(t.actionableCategory)-e.indexOf(n.actionableCategory)||n.updatedDate.getTime()-t.updatedDate.getTime())}function c(e){if(e==null||e.length===0)return new Map;let t=new Map(i.map(e=>[e,[]]));s(e);for(let n of e){let e=o(n);for(let r of e)t.get(r)?.push(n)}return t.get(`needs-review`)?.sort((e,t)=>(e.isDraft?1:-1)-(t.isDraft?1:-1)),t.get(`pinned`)?.sort((e,t)=>t.updatedDate.getTime()-e.updatedDate.getTime()),t.get(`draft`)?.sort((e,t)=>t.updatedDate.getTime()-e.updatedDate.getTime()),t}function l(e){if(e==null||e.length===0)return new Map;let n=new Map(i.map(e=>[e,0]));function r(e){n.set(e,(n.get(e)??0)+1)}for(let n of e){if(n.viewer.snoozed){r(`snoozed`);continue}else n.viewer.pinned&&r(`pinned`);n.isDraft?r(`draft`):r(a(t,n.actionableCategory))}return n}function u(e,t,n){let i=[...a(r,e)];return n&&i.push(`start-review`),t?i.push(`show-overview`,`open-changes`,`open-in-graph`):i.push(`open-worktree`,`switch`,`open-in-graph`),i}export{l as countLaunchpadItemGroups,o as getLaunchpadItemGroups,u as getSuggestedActions,c as groupAndSortLaunchpadItems,s as sortLaunchpadItems};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/models/launchpad.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Launchpad priority-model tables, ported from GitLens `origin/main`
|
|
4
|
+
* `src/plus/launchpad/models/launchpad.ts` (gkcli#1726 P5a).
|
|
5
|
+
*
|
|
6
|
+
* `@vscode/l10n` calls are replaced with plain English strings and
|
|
7
|
+
* `@gitlens/utils/array.js`'s `exhaustiveArray` with the local
|
|
8
|
+
* {@link exhaustiveArray} helper below. GitLens' VS Code codicon map
|
|
9
|
+
* (`launchpadGroupIconMap`) is not ported: it is presentation-only for the VS
|
|
10
|
+
* Code UI and has no meaning in a runtime-agnostic library.
|
|
11
|
+
*/
|
|
12
|
+
declare const launchpadActionCategories: readonly ["mergeable", "unassigned-reviewers", "failed-checks", "conflicts", "needs-my-review", "changes-requested", "reviewer-commented", "waiting-for-review", "draft", "other"];
|
|
13
|
+
type LaunchpadActionCategory = (typeof launchpadActionCategories)[number];
|
|
14
|
+
declare const launchpadGroups: readonly ["current-branch", "pinned", "mergeable", "blocked", "follow-up", "needs-review", "waiting-for-review", "draft", "other", "snoozed"];
|
|
15
|
+
type LaunchpadGroup = (typeof launchpadGroups)[number];
|
|
16
|
+
declare const launchpadPriorityGroups: readonly LaunchpadGroup[];
|
|
17
|
+
type LaunchpadPriorityGroup = Extract<LaunchpadGroup, 'mergeable' | 'blocked' | 'follow-up' | 'needs-review'>;
|
|
18
|
+
/** Optional English presentation helpers (GitLens wording). Library results are keyed by the category/group values; consumers can render their own text. */
|
|
19
|
+
declare const launchpadGroupLabelMap: Map<"mergeable" | "waiting-for-review" | "draft" | "other" | "current-branch" | "pinned" | "blocked" | "follow-up" | "needs-review" | "snoozed", string>;
|
|
20
|
+
declare const launchpadCategoryToGroupMap: Map<"mergeable" | "unassigned-reviewers" | "failed-checks" | "conflicts" | "needs-my-review" | "changes-requested" | "reviewer-commented" | "waiting-for-review" | "draft" | "other", "mergeable" | "waiting-for-review" | "draft" | "other" | "current-branch" | "pinned" | "blocked" | "follow-up" | "needs-review" | "snoozed">;
|
|
21
|
+
declare const sharedCategoryToLaunchpadActionCategoryMap: Map<string, "mergeable" | "unassigned-reviewers" | "failed-checks" | "conflicts" | "needs-my-review" | "changes-requested" | "reviewer-commented" | "waiting-for-review" | "draft" | "other">;
|
|
22
|
+
type LaunchpadAction = 'merge' | 'open' | 'soft-open' | 'switch' | 'open-worktree' | 'start-review' | 'show-overview' | 'open-changes' | 'open-in-graph';
|
|
23
|
+
declare const prActionsMap: Map<"mergeable" | "unassigned-reviewers" | "failed-checks" | "conflicts" | "needs-my-review" | "changes-requested" | "reviewer-commented" | "waiting-for-review" | "draft" | "other", LaunchpadAction[]>;
|
|
24
|
+
/** Optional English presentation helpers (GitLens wording). Library results are keyed by the category/group values; consumers can render their own text. */
|
|
25
|
+
declare const actionGroupMap: Map<"mergeable" | "unassigned-reviewers" | "failed-checks" | "conflicts" | "needs-my-review" | "changes-requested" | "reviewer-commented" | "waiting-for-review" | "draft" | "other", readonly [label: string, detail: (author: string | null, createdDateRelative: string | null) => string]>;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { LaunchpadAction, LaunchpadActionCategory, LaunchpadGroup, LaunchpadPriorityGroup, actionGroupMap, launchpadActionCategories, launchpadCategoryToGroupMap, launchpadGroupLabelMap, launchpadGroups, launchpadPriorityGroups, prActionsMap, sharedCategoryToLaunchpadActionCategoryMap };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(){return(...e)=>e[0]}function t(e){return new Map(Object.entries(e))}const n=[`mergeable`,`unassigned-reviewers`,`failed-checks`,`conflicts`,`needs-my-review`,`changes-requested`,`reviewer-commented`,`waiting-for-review`,`draft`,`other`],r=[`current-branch`,`pinned`,`mergeable`,`blocked`,`follow-up`,`needs-review`,`waiting-for-review`,`draft`,`other`,`snoozed`],i=e()([`mergeable`,`blocked`,`follow-up`,`needs-review`]),a=t({"current-branch":`Current Branch`,pinned:`Pinned`,mergeable:`Ready to Merge`,blocked:`Blocked`,"follow-up":`Requires Follow-up`,"needs-review":`Needs Your Review`,"waiting-for-review":`Waiting for Review`,draft:`Draft`,other:`Other`,snoozed:`Snoozed`}),o=t({mergeable:`mergeable`,conflicts:`blocked`,"failed-checks":`blocked`,"unassigned-reviewers":`blocked`,"needs-my-review":`needs-review`,"changes-requested":`follow-up`,"reviewer-commented":`follow-up`,"waiting-for-review":`waiting-for-review`,draft:`draft`,other:`other`}),s=new Map([[`readyToMerge`,`mergeable`],[`unassignedReviewers`,`unassigned-reviewers`],[`failingCI`,`failed-checks`],[`conflicts`,`conflicts`],[`needsMyReview`,`needs-my-review`],[`changesRequested`,`changes-requested`],[`reviewerCommented`,`reviewer-commented`],[`waitingForReview`,`waiting-for-review`],[`draft`,`draft`],[`other`,`other`]]),c=t({mergeable:[`merge`],"unassigned-reviewers":[`open`],"failed-checks":[`open`],conflicts:[`open`],"needs-my-review":[`open`],"changes-requested":[`open`],"reviewer-commented":[`open`],"waiting-for-review":[`open`],draft:[`open`],other:[]}),l=t({mergeable:[`Ready to Merge`,()=>`Ready to merge`],"unassigned-reviewers":[`Unassigned Reviewers`,()=>`You need to assign reviewers`],"failed-checks":[`Failed Checks`,()=>`You need to resolve the failing checks`],conflicts:[`Resolve Conflicts`,()=>`You need to resolve merge conflicts`],"needs-my-review":[`Needs Your Review`,e=>e==null?`An unknown author requested your review`:`${e} requested your review`],"changes-requested":[`Changes Requested`,()=>`Reviewers requested changes before this can be merged`],"reviewer-commented":[`Reviewers Commented`,()=>`Reviewers have commented on this pull request`],"waiting-for-review":[`Waiting for Review`,()=>`Waiting for reviewers to approve this pull request`],draft:[`Draft`,()=>`Continue working on your draft`],other:[`Other`,(e,t)=>e==null?t==null?`Opened`:`Opened ${t}`:t==null?`Opened by ${e}`:`Opened by ${e} ${t}`]});export{l as actionGroupMap,n as launchpadActionCategories,o as launchpadCategoryToGroupMap,a as launchpadGroupLabelMap,r as launchpadGroups,i as launchpadPriorityGroups,c as prActionsMap,s as sharedCategoryToLaunchpadActionCategoryMap};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { GitHostingProviderId } from "./providers.js";
|
|
2
|
+
import { ApiRequest, LoggerPort } from "@gitkraken/api-tools";
|
|
3
|
+
|
|
4
|
+
//#region src/provider-tokens.d.ts
|
|
5
|
+
/** The GitKraken API connection. Structurally satisfied by account-tools' `AccountClientConfig`. */
|
|
6
|
+
interface IntegrationsClientConfig {
|
|
7
|
+
/** The GitKraken API root, with a trailing `/` (e.g. `https://api.gitkraken.com/`). */
|
|
8
|
+
apiBaseUrl: string;
|
|
9
|
+
/** Called per request for the caller's GitKraken bearer token. */
|
|
10
|
+
getToken: () => Promise<string>;
|
|
11
|
+
request?: ApiRequest;
|
|
12
|
+
logger?: LoggerPort;
|
|
13
|
+
}
|
|
14
|
+
/** A git provider connection's token, as the GitKraken API stores it. */
|
|
15
|
+
interface ProviderToken {
|
|
16
|
+
provider: GitHostingProviderId;
|
|
17
|
+
accessToken: string;
|
|
18
|
+
/** The raw connection type (`oauth`, `pat`, ...). */
|
|
19
|
+
type: string;
|
|
20
|
+
/** Whether the token is a personal access token (`type` is `pat`, case-insensitively). */
|
|
21
|
+
isPat: boolean;
|
|
22
|
+
/** The server URL of a self-hosted connection. */
|
|
23
|
+
domain?: string;
|
|
24
|
+
tokenId?: string;
|
|
25
|
+
isPrimary?: boolean;
|
|
26
|
+
expiresIn?: number;
|
|
27
|
+
/** The Bitbucket Cloud app-password login. The cloud does not return it; the host supplies it. */
|
|
28
|
+
username?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Fetches the caller's token for a git provider via `GET v1/provider-tokens/{id}`. Nothing is
|
|
32
|
+
* cached or persisted; the caller decides when to refresh (see {@link refreshProviderToken}).
|
|
33
|
+
*
|
|
34
|
+
* Throws {@link IntegrationError}: `not_connected` (HTTP 404, or no access token), `not_logged_in`
|
|
35
|
+
* (HTTP 401), `invalid_request` (another 4xx) or `provider_error` (5xx, network, unexpected body).
|
|
36
|
+
*/
|
|
37
|
+
declare function getProviderToken(config: IntegrationsClientConfig, provider: GitHostingProviderId, signal?: AbortSignal): Promise<ProviderToken>;
|
|
38
|
+
/**
|
|
39
|
+
* Rotates an expiring OAuth token via `POST v1/provider-tokens/{id}/refresh` and returns the new
|
|
40
|
+
* access token. Not meant for PATs or self-hosted servers. Same error mapping as
|
|
41
|
+
* {@link getProviderToken}; an empty token in the response is a `provider_error`.
|
|
42
|
+
*/
|
|
43
|
+
declare function refreshProviderToken(config: IntegrationsClientConfig, provider: GitHostingProviderId, accessToken: string, signal?: AbortSignal): Promise<string>;
|
|
44
|
+
//#endregion
|
|
45
|
+
export { IntegrationsClientConfig, ProviderToken, getProviderToken, refreshProviderToken };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{IntegrationError as e}from"./errors.js";import{asRecord as t,asString as n,isAbortError as r}from"./internal/json.js";import{providerDisplayName as i}from"./providers.js";import{HttpError as a,Transport as o}from"@gitkraken/api-tools";async function s(e,r,i){let a=t((await d(e,r,{method:`GET`,path:l(r),signal:i}))?.data),o=n(a?.accessToken);if(a==null||o==null)throw u(r);let s=typeof a.type==`string`?a.type:``,c={provider:r,accessToken:o,type:s,isPat:/^pat$/i.test(s)},f=n(a.domain);f!=null&&(c.domain=f);let p=n(a.tokenId);return p!=null&&(c.tokenId=p),typeof a.isPrimary==`boolean`&&(c.isPrimary=a.isPrimary),typeof a.expiresIn==`number`&&(c.expiresIn=a.expiresIn),c}async function c(r,a,o,s){let c=n(t((await d(r,a,{method:`POST`,path:`${l(a)}/refresh`,body:{access_token:o},signal:s}))?.data)?.accessToken);if(c==null)throw new e(`provider_error`,`GitKraken returned no refreshed token for ${i(a)}`,{provider:a});return c}function l(e){return`v1/provider-tokens/${encodeURIComponent(e)}`}function u(t,n,r){return new e(`not_connected`,`${i(t)} is not connected to your GitKraken account`,{provider:t,status:n,cause:r})}async function d(n,r,i){let a=new o({baseUrl:n.apiBaseUrl,getToken:n.getToken,request:n.request,logger:n.logger}),s;try{({data:s}=await a.request({...i,envelope:!1}))}catch(e){throw f(e,r,i.signal)}let c=t(s);if(c==null)throw new e(`provider_error`,`GitKraken returned an unexpected response`,{provider:r});return c}function f(i,o,s){if(i instanceof e||r(i)||s?.aborted)return i;if(!(i instanceof a))return new e(`provider_error`,i instanceof Error?i.message:String(i),{provider:o,cause:i});let{status:c}=i;return c===404?u(o,c,i):c===401?new e(`not_logged_in`,`you are not logged in to GitKraken`,{provider:o,status:c,cause:i}):c===429?new e(`rate_limited`,`GitKraken rate limited the request (HTTP 429)`,{provider:o,status:c,cause:i}):c>=400&&c<500?new e(`invalid_request`,n(t(t(i.body)?.error)?.message)??`HTTP ${c}`,{provider:o,status:c,cause:i}):new e(`provider_error`,`GitKraken request failed (HTTP ${c})`,{provider:o,status:c,cause:i})}export{s as getProviderToken,c as refreshProviderToken};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/providers.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The git-hosting providers integrations-tools can resolve, fetch tokens for and open pull
|
|
4
|
+
* requests on. The ids are the camelCase ids the GitKraken cloud API uses in
|
|
5
|
+
* `v1/provider-tokens/{id}` (gkcli `NormalizeProviderName`).
|
|
6
|
+
*/
|
|
7
|
+
type GitHostingProviderId = 'github' | 'githubEnterprise' | 'gitlab' | 'gitlabSelfHosted' | 'bitbucket' | 'bitbucketServer' | 'azure' | 'azureDevopsServer';
|
|
8
|
+
/**
|
|
9
|
+
* Maps a provider name to its {@link GitHostingProviderId}, case-insensitively and ignoring `_`.
|
|
10
|
+
* Accepts storage keys (`GITHUBENTERPRISE`), CLI names (`github_enterprise`) and cloud ids
|
|
11
|
+
* (`githubEnterprise`). Returns `undefined` for anything else (`jira`, `''`, ...).
|
|
12
|
+
*/
|
|
13
|
+
declare function normalizeProviderId(name: string): GitHostingProviderId | undefined;
|
|
14
|
+
/** The human-readable provider name (gkcli `DisplayName`): `GitHub Enterprise`, `Azure DevOps Server`, ... */
|
|
15
|
+
declare function providerDisplayName(id: GitHostingProviderId): string;
|
|
16
|
+
/** Whether the provider is a self-hosted server whose URL comes from the user's connection. */
|
|
17
|
+
declare function isSelfHosted(id: GitHostingProviderId): boolean;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { GitHostingProviderId, isSelfHosted, normalizeProviderId, providerDisplayName };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e={github:`github`,githubenterprise:`githubEnterprise`,gitlab:`gitlab`,gitlabselfhosted:`gitlabSelfHosted`,bitbucket:`bitbucket`,bitbucketserver:`bitbucketServer`,azure:`azure`,azuredevopsserver:`azureDevopsServer`},t={github:`GitHub`,githubEnterprise:`GitHub Enterprise`,gitlab:`GitLab`,gitlabSelfHosted:`GitLab Self-Hosted`,bitbucket:`Bitbucket`,bitbucketServer:`Bitbucket Server`,azure:`Azure DevOps`,azureDevopsServer:`Azure DevOps Server`},n=new Set([`githubEnterprise`,`gitlabSelfHosted`,`bitbucketServer`,`azureDevopsServer`]);function r(t){let n=t.trim().replaceAll(`_`,``).toLowerCase();return Object.hasOwn(e,n)?e[n]:void 0}function i(e){return t[e]}function a(e){return n.has(e)}export{a as isSelfHosted,r as normalizeProviderId,i as providerDisplayName};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PullRequestAdapter } from "./shared.js";
|
|
2
|
+
|
|
3
|
+
//#region src/pull-requests/azure.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Azure DevOps and Azure DevOps Server:
|
|
6
|
+
* `POST {orgURL}/{project}/_apis/git/repositories/{repo}/pullrequests?api-version=...`. The cloud's
|
|
7
|
+
* `orgURL` is `https://dev.azure.com/{org}`; a Server's is its collection URL. PATs use Basic
|
|
8
|
+
* `:token` (Server tokens are always PATs); OAuth uses Bearer.
|
|
9
|
+
*/
|
|
10
|
+
declare const azureAdapter: PullRequestAdapter;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { azureAdapter };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{asRecord as e}from"../internal/json.js";import{basicAuth as t,checkSegments as n,encode as r,invalidRequest as i,serverRoot as a}from"./shared.js";const o=`Azure DevOps Server needs its collection URL (https://host[:port]/[<virtual-dir>/]<Collection>)`,s={prepare(e,i){let a=e.provider===`azureDevopsServer`;a||n(e,[`owner`,e.owner]),n(e,[`azureProject`,e.azureProject??``],[`repo`,e.repo]);let o=c(e.provider,e.owner,i.domain),s=a||i.isPat;return{baseUrl:`${o}/`,path:`${r(e.azureProject??``)}/_apis/git/repositories/${r(e.repo)}/pullrequests?api-version=${a?`7.0`:`7.1`}`,headers:{Authorization:s?t(``,i.accessToken):`Bearer ${i.accessToken}`,"X-TFS-FedAuthRedirect":`Suppress`},body:{sourceRefName:`refs/heads/${e.head}`,targetRefName:`refs/heads/${e.base}`,title:e.title,description:e.body,isDraft:e.draft??!1,completionOptions:{deleteSourceBranch:!0}}}},errorMessage(t){let n=e(t)?.message;return typeof n==`string`&&n!==``?n:void 0},authChallenge(e){return e===203},result(t,n,i){let a=e(t),o=a?.pullRequestId;if(a==null||typeof o!=`number`)return;let s=c(n.provider,n.owner,i.domain);return{provider:n.provider,owner:n.owner,repo:n.repo,id:String(o),number:o,url:`${s}/${r(n.azureProject??``)}/_git/${r(n.repo)}/pullrequest/${o}`,draft:a.isDraft===!0}}};function c(e,t,n){if(e!==`azureDevopsServer`)return`https://dev.azure.com/${r(t)}`;let s;try{s=a(n,`azureDevopsServer`)}catch{throw i(o,`azureDevopsServer`)}let c=new URL(s).pathname.split(`/`).slice(1);if(c.length===0||c.some(e=>e===``||e.startsWith(`_`)))throw i(o,`azureDevopsServer`);return s}export{s as azureAdapter};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PullRequestAdapter } from "./shared.js";
|
|
2
|
+
|
|
3
|
+
//#region src/pull-requests/bitbucket-server.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Bitbucket Server / Data Center: `POST {server}/rest/api/1.0/projects/{KEY}/repos/{slug}/pull-requests`
|
|
6
|
+
* with Bearer auth. The REST API has no draft flag before Data Center 8.18, so drafts are refused
|
|
7
|
+
* before this adapter runs (see `supportsDraft`).
|
|
8
|
+
*/
|
|
9
|
+
declare const bitbucketServerAdapter: PullRequestAdapter;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { bitbucketServerAdapter };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{asRecord as e,asString as t}from"../internal/json.js";import{checkSegments as n,encode as r,messageOf as i,serverRoot as a}from"./shared.js";const o={prepare(e,t){n(e,[`owner`,e.owner],[`repo`,e.repo]);let i=a(t.domain,e.provider),o=t=>({id:`refs/heads/${t}`,repository:{slug:e.repo,project:{key:e.owner}}});return{baseUrl:`${i}/rest/api/1.0/`,path:`projects/${r(e.owner)}/repos/${r(e.repo)}/pull-requests`,headers:{Authorization:`Bearer ${t.accessToken}`},body:{title:e.title,description:e.body,state:`OPEN`,fromRef:o(e.head),toRef:o(e.base)}}},errorMessage(t){let n=e(t)?.errors,r=Array.isArray(n)?n.map(i).filter(e=>e!=null):[];return r.length>0?r.join(`; `):void 0},result(n,i,o){let c=e(n),l=c?.id;if(c==null||typeof l!=`number`)return;let u=e(s(e(c.links)?.self)),d=a(o.domain,i.provider);return{provider:i.provider,owner:i.owner,repo:i.repo,id:String(l),number:l,url:t(u?.href)??`${d}/projects/${r(i.owner)}/repos/${r(i.repo)}/pull-requests/${l}`,draft:!1}}};function s(e){return Array.isArray(e)?e[0]:void 0}export{o as bitbucketServerAdapter};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { PullRequestAdapter } from "./shared.js";
|
|
2
|
+
|
|
3
|
+
//#region src/pull-requests/bitbucket.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Bitbucket Cloud: `POST repositories/{workspace}/{slug}/pullrequests`. OAuth uses Bearer; an app
|
|
6
|
+
* password (PAT) uses Basic `username:token`, and the cloud does not return the username.
|
|
7
|
+
*/
|
|
8
|
+
declare const bitbucketAdapter: PullRequestAdapter;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { bitbucketAdapter };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{asRecord as e,asString as t}from"../internal/json.js";import{basicAuth as n,checkSegments as r,encode as i,invalidRequest as a}from"./shared.js";const o={prepare(e,t){if(r(e,[`owner`,e.owner],[`repo`,e.repo]),t.isPat&&(t.username==null||t.username===``))throw a(`Bitbucket app passwords need the account username; reconnect Bitbucket with your username`,e.provider);return{baseUrl:`https://api.bitbucket.org/2.0/`,path:`repositories/${i(e.owner)}/${i(e.repo)}/pullrequests`,headers:{Authorization:t.isPat?n(t.username??``,t.accessToken):`Bearer ${t.accessToken}`},body:{title:e.title,description:e.body,source:{branch:{name:e.head}},destination:{branch:{name:e.base}},close_source_branch:!0,draft:e.draft??!1}}},errorMessage(n){let r=e(e(n)?.error),i=t(r?.message),a=t(r?.detail);return i!=null&&a!=null?`${i}: ${a}`:i??a},result(n,r){let i=e(n),a=t(e(e(i?.links)?.html)?.href),o=i?.id;if(!(i==null||a==null||typeof o!=`number`))return{provider:r.provider,owner:r.owner,repo:r.repo,id:String(o),number:o,url:a,draft:i.draft===!0}}};export{o as bitbucketAdapter};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { PullRequestAdapter } from "./shared.js";
|
|
2
|
+
|
|
3
|
+
//#region src/pull-requests/github.d.ts
|
|
4
|
+
/** GitHub and GitHub Enterprise: `POST repos/{owner}/{repo}/pulls`, Bearer auth for OAuth and PAT alike. */
|
|
5
|
+
declare const githubAdapter: PullRequestAdapter;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { githubAdapter };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{asRecord as e,asString as t}from"../internal/json.js";import{isSelfHosted as n}from"../providers.js";import{checkSegments as r,encode as i,messageOf as a,serverRoot as o,withApiPath as s}from"./shared.js";const c={prepare(e,t){return r(e,[`owner`,e.owner],[`repo`,e.repo]),{baseUrl:n(e.provider)?s(o(t.domain,e.provider),`/api/v3`):`https://api.github.com/`,path:`repos/${i(e.owner)}/${i(e.repo)}/pulls`,headers:{Authorization:`Bearer ${t.accessToken}`,Accept:`application/vnd.github+json`,"X-GitHub-Api-Version":`2022-11-28`},body:{title:e.title,body:e.body,head:e.head,base:e.base,draft:e.draft??!1,maintainer_can_modify:!0}}},errorMessage(n){let r=e(n),i=Array.isArray(r?.errors)?r.errors.map(u).filter(d):[],a=t(r?.message);return a!=null&&i.length>0?`${a}: ${i.join(`; `)}`:a??(i.length>0?i.join(`; `):void 0)},rateLimited(e,t){return e===403&&(t.get(`x-ratelimit-remaining`)===`0`||t.has(`retry-after`))},result(n,r){let i=e(n),a=t(i?.html_url),o=i?.number;if(!(i==null||a==null||typeof o!=`number`))return l(r,i,a,o)}};function l(e,t,n,r){return{provider:e.provider,owner:e.owner,repo:e.repo,id:String(t.id??r),number:r,url:n,draft:t.draft===!0}}function u(t){let n=a(t);if(n!=null)return n;let r=e(t),i=[r?.resource,r?.field,r?.code].filter(e=>typeof e==`string`&&e!==``);return i.length>0?i.join(` `):void 0}function d(e){return e!=null}export{c as githubAdapter};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { PullRequestAdapter } from "./shared.js";
|
|
2
|
+
|
|
3
|
+
//#region src/pull-requests/gitlab.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* GitLab and self-hosted GitLab: `POST projects/{owner/name}/merge_requests`. A PAT goes in
|
|
6
|
+
* `PRIVATE-TOKEN`, an OAuth token in `Authorization: Bearer`.
|
|
7
|
+
*/
|
|
8
|
+
declare const gitlabAdapter: PullRequestAdapter;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { gitlabAdapter };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{asRecord as e,asString as t}from"../internal/json.js";import{isSelfHosted as n}from"../providers.js";import{checkSegments as r,encode as i,serverRoot as a,withApiPath as o}from"./shared.js";function s(e){return e.isPat?{"PRIVATE-TOKEN":e.accessToken}:{Authorization:`Bearer ${e.accessToken}`}}const c=/^\s*(?:draft:|\[draft\]|\(draft\))/i,l={prepare(e,t){return r(e,[`owner`,e.owner],[`repo`,e.repo]),{baseUrl:n(e.provider)?o(a(t.domain,e.provider),`/api/v4`):`https://gitlab.com/api/v4/`,path:`projects/${i(`${e.owner}/${e.repo}`)}/merge_requests`,headers:s(t),body:{title:e.draft===!0&&!c.test(e.title)?`Draft: ${e.title}`:e.title,description:e.body,source_branch:e.head,target_branch:e.base,remove_source_branch:!0}}},errorMessage(n){let r=e(n);return u(r?.message)??t(r?.error_description)??t(r?.error)??void 0},result(n,r){let i=e(n),a=t(i?.web_url),o=i?.iid;if(!(i==null||a==null||typeof o!=`number`))return{provider:r.provider,owner:r.owner,repo:r.repo,id:String(i.id??o),number:o,url:a,draft:i.draft===!0||i.work_in_progress===!0}}};function u(e){if(typeof e==`string`)return e===``?void 0:e;let t=(Array.isArray(e)?e.map(u):typeof e==`object`&&e?Object.values(e).map(u):[]).filter(e=>e!=null).join(`; `);return t===``?void 0:t}export{l as gitlabAdapter};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { GitHostingProviderId } from "../providers.js";
|
|
2
|
+
import { IntegrationError } from "../errors.js";
|
|
3
|
+
import { ProviderToken } from "../provider-tokens.js";
|
|
4
|
+
import { ApiRequest, LoggerPort } from "@gitkraken/api-tools";
|
|
5
|
+
|
|
6
|
+
//#region src/pull-requests/shared.d.ts
|
|
7
|
+
interface CreatePullRequestInput {
|
|
8
|
+
provider: GitHostingProviderId;
|
|
9
|
+
/**
|
|
10
|
+
* The repository owner as {@link RemoteRepository.owner} gives it: the GitHub owner, the full GitLab
|
|
11
|
+
* namespace (`group/sub`), the Bitbucket workspace or Bitbucket Server project key, the Azure DevOps
|
|
12
|
+
* organization (cloud) or collection name (Server).
|
|
13
|
+
*/
|
|
14
|
+
owner: string;
|
|
15
|
+
/** The repository name (the slug on Bitbucket). */
|
|
16
|
+
repo: string;
|
|
17
|
+
/** Required for `azure` and `azureDevopsServer`. */
|
|
18
|
+
azureProject?: string;
|
|
19
|
+
/** The branch the pull request merges into. */
|
|
20
|
+
base: string;
|
|
21
|
+
/** The branch the pull request merges from. */
|
|
22
|
+
head: string;
|
|
23
|
+
title: string;
|
|
24
|
+
body: string;
|
|
25
|
+
draft?: boolean;
|
|
26
|
+
}
|
|
27
|
+
interface CreatePullRequestOptions {
|
|
28
|
+
/** The connection's token, from `getProviderToken` (plus `username` for a Bitbucket Cloud PAT). */
|
|
29
|
+
token: ProviderToken;
|
|
30
|
+
/** The host-owned network. Defaults to `fetch`, as in `@gitkraken/api-tools`. */
|
|
31
|
+
request?: ApiRequest;
|
|
32
|
+
logger?: LoggerPort;
|
|
33
|
+
signal?: AbortSignal;
|
|
34
|
+
}
|
|
35
|
+
interface CreatedPullRequest {
|
|
36
|
+
provider: GitHostingProviderId;
|
|
37
|
+
owner: string;
|
|
38
|
+
repo: string;
|
|
39
|
+
/** The provider's own id for the pull request. */
|
|
40
|
+
id: string;
|
|
41
|
+
/** The number shown in the provider's UI (GitLab `iid`, Azure `pullRequestId`). */
|
|
42
|
+
number: number;
|
|
43
|
+
/** The browser URL of the pull request. */
|
|
44
|
+
url: string;
|
|
45
|
+
draft: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** One provider API call, relative to `baseUrl` (which ends in `/`). */
|
|
48
|
+
interface PreparedRequest {
|
|
49
|
+
baseUrl: string;
|
|
50
|
+
path: string;
|
|
51
|
+
headers: Record<string, string>;
|
|
52
|
+
body: unknown;
|
|
53
|
+
}
|
|
54
|
+
/** A provider's pull-request API: how to address it, and how to read what it answers. */
|
|
55
|
+
interface PullRequestAdapter {
|
|
56
|
+
/** Validates the input and builds the call. Throws {@link IntegrationError} before any request is made. */
|
|
57
|
+
prepare(input: CreatePullRequestInput, token: ProviderToken): PreparedRequest;
|
|
58
|
+
/** The provider's message from a non-2xx body. */
|
|
59
|
+
errorMessage(body: unknown): string | undefined;
|
|
60
|
+
/** Whether a non-2xx response is the provider throttling the caller rather than refusing it. */
|
|
61
|
+
rateLimited?(status: number, headers: Headers): boolean;
|
|
62
|
+
/** Whether a 2xx status is really the provider refusing the token (Azure DevOps answers a bad token with a 203 sign-in page). */
|
|
63
|
+
authChallenge?(status: number): boolean;
|
|
64
|
+
/** Maps a 2xx body; `undefined` when it lacks the id or url. */
|
|
65
|
+
result(body: unknown, input: CreatePullRequestInput, token: ProviderToken): CreatedPullRequest | undefined;
|
|
66
|
+
}
|
|
67
|
+
declare function invalidRequest(message: string, provider: GitHostingProviderId): IntegrationError;
|
|
68
|
+
/** Rejects empty or dot-only coordinates, which would silently change the request path. */
|
|
69
|
+
declare function checkSegments(input: CreatePullRequestInput, ...values: [label: string, value: string][]): void;
|
|
70
|
+
/**
|
|
71
|
+
* The normalized root of a self-hosted server (no trailing `/`, no query), from a connection's `domain`.
|
|
72
|
+
* A domain without a scheme is taken as `https`.
|
|
73
|
+
*/
|
|
74
|
+
declare function serverRoot(domain: string | undefined, provider: GitHostingProviderId): string;
|
|
75
|
+
/** `{root}/{suffix}/`, unless `root` already ends with `suffix` (e.g. a domain saved as `https://host/api/v3`). */
|
|
76
|
+
declare function withApiPath(root: string, suffix: string): string;
|
|
77
|
+
declare function encode(value: string): string;
|
|
78
|
+
/** An HTTP Basic `Authorization` header value; the credentials are encoded as UTF-8 bytes. */
|
|
79
|
+
declare function basicAuth(user: string, password: string): string;
|
|
80
|
+
/** An error-body item that is a string, or an object with a `message`. */
|
|
81
|
+
declare function messageOf(value: unknown): string | undefined;
|
|
82
|
+
//#endregion
|
|
83
|
+
export { CreatePullRequestInput, CreatePullRequestOptions, CreatedPullRequest, PreparedRequest, PullRequestAdapter, basicAuth, checkSegments, encode, invalidRequest, messageOf, serverRoot, withApiPath };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{IntegrationError as e}from"../errors.js";import{asRecord as t,asString as n}from"../internal/json.js";import{providerDisplayName as r}from"../providers.js";function i(t,n){return new e(`invalid_request`,t,{provider:n})}function a(e,...t){for(let[n,a]of t)if(a.trim()===``||a===`.`||a===`..`)throw i(`${n} is required to create a pull request on ${r(e.provider)}`,e.provider)}function o(e,t){let n=()=>i(`${r(t)} URL is unknown for this connection`,t),a=e?.trim();if(a==null||a===``)throw n();let o;try{o=new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(a)?a:`https://${a}`)}catch{throw n()}if(o.protocol!==`https:`&&o.protocol!==`http:`)throw n();return`${o.protocol}//${o.host}${o.pathname.replace(/\/+$/,``)}`}function s(e,t){return e.toLowerCase().endsWith(t)?`${e}/`:`${e}${t}/`}function c(e){return encodeURIComponent(e)}function l(e,t){let n=new TextEncoder().encode(`${e}:${t}`),r=``;for(let e of n)r+=String.fromCharCode(e);return`Basic ${btoa(r)}`}function u(e){return n(e)??n(t(e)?.message)}export{l as basicAuth,a as checkSegments,c as encode,i as invalidRequest,u as messageOf,o as serverRoot,s as withApiPath};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { GitHostingProviderId } from "./providers.js";
|
|
2
|
+
import { CreatePullRequestInput, CreatePullRequestOptions, CreatedPullRequest } from "./pull-requests/shared.js";
|
|
3
|
+
|
|
4
|
+
//#region src/pull-requests.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Whether the provider can open a draft pull request. `false` only for Bitbucket Server, which
|
|
7
|
+
* (before Data Center 8.18) has no draft state and would silently open a ready pull request.
|
|
8
|
+
*/
|
|
9
|
+
declare function supportsDraft(provider: GitHostingProviderId): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Opens a pull request (a merge request on GitLab) on the provider the input names, using the
|
|
12
|
+
* connection's token from `getProviderToken`. One call per invocation: no push, no assignee, no
|
|
13
|
+
* retry and no token refresh (a `provider_auth` error tells the caller to refresh and retry).
|
|
14
|
+
*
|
|
15
|
+
* GitLab merge requests are opened with "delete source branch on merge" set; GitHub has no
|
|
16
|
+
* equivalent.
|
|
17
|
+
*
|
|
18
|
+
* Throws {@link IntegrationError}: `unsupported` and `invalid_request` for input the provider
|
|
19
|
+
* cannot take (no request is sent), `provider_auth` for a rejected token (HTTP 401/403, or Azure's
|
|
20
|
+
* 203 sign-in page), `rate_limited` (HTTP 429, or GitHub's exhausted primary or secondary limit),
|
|
21
|
+
* `invalid_request` for any other 4xx (the provider's message, e.g. "a pull request already
|
|
22
|
+
* exists") and `provider_error` for a 5xx or a network failure (retryable), or for a 2xx whose
|
|
23
|
+
* body has no id or url or is not valid JSON (not retryable: the pull request may have been created).
|
|
24
|
+
*/
|
|
25
|
+
declare function createPullRequest(input: CreatePullRequestInput, options: CreatePullRequestOptions): Promise<CreatedPullRequest>;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { type CreatePullRequestInput, type CreatePullRequestOptions, type CreatedPullRequest, createPullRequest, supportsDraft };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{IntegrationError as e}from"./errors.js";import{isAbortError as t}from"./internal/json.js";import{providerDisplayName as n}from"./providers.js";import{invalidRequest as r}from"./pull-requests/shared.js";import{azureAdapter as i}from"./pull-requests/azure.js";import{bitbucketAdapter as a}from"./pull-requests/bitbucket.js";import{bitbucketServerAdapter as o}from"./pull-requests/bitbucket-server.js";import{githubAdapter as s}from"./pull-requests/github.js";import{gitlabAdapter as c}from"./pull-requests/gitlab.js";import{HttpError as l,Transport as u}from"@gitkraken/api-tools";function d(e){return e!==`bitbucketServer`}async function f(t,i){let{token:a}=i,o=p(t.provider);if(a.provider!==t.provider)throw r(`the ${n(a.provider)} token cannot open a pull request on ${n(t.provider)}`,t.provider);if(t.draft===!0&&!d(t.provider))throw new e(`unsupported`,`draft pull requests are not supported for ${n(t.provider)}`,{provider:t.provider});let s=o.prepare(t,a),c=new u({baseUrl:s.baseUrl,getToken:async()=>a.accessToken,request:i.request,logger:i.logger}),l;try{l=await c.request({method:`POST`,path:s.path,headers:s.headers,body:s.body,auth:!1,envelope:!1,signal:i.signal})}catch(e){throw h(e,t.provider,o)}if(o.authChallenge?.(l.status)===!0)throw new e(`provider_auth`,`${n(t.provider)} did not accept the connection's token (it answered with a sign-in page)`,{provider:t.provider,status:l.status});let f=o.result(l.data,t,a);if(f==null)throw m(t.provider,l.status);return f}function p(e){switch(e){case`github`:case`githubEnterprise`:return s;case`gitlab`:case`gitlabSelfHosted`:return c;case`bitbucket`:return a;case`bitbucketServer`:return o;case`azure`:case`azureDevopsServer`:return i}}function m(t,r,i){return new e(`provider_error`,`${n(t)} returned an unexpected response; the pull request may have been created`,{provider:t,status:r,cause:i,retryable:!1})}function h(n,r,i){if(n instanceof e||t(n))return n;if(!(n instanceof l))return new e(`provider_error`,n instanceof Error?n.message:String(n),{provider:r,cause:n});let{status:a}=n;if(a<300)return m(r,a,n);let o=i.errorMessage(n.body)??`HTTP ${a}`,s={provider:r,status:a,cause:n};return a===429||a>=400&&a<500&&i.rateLimited?.(a,n.headers)===!0?new e(`rate_limited`,o,s):a===401||a===403?new e(`provider_auth`,o,s):a>=400&&a<500?new e(`invalid_request`,o,s):new e(`provider_error`,o,{...s,retryable:a>=500})}export{f as createPullRequest,d as supportsDraft};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { GitHostingProviderId } from "./providers.js";
|
|
2
|
+
|
|
3
|
+
//#region src/remotes.d.ts
|
|
4
|
+
/** A connected self-hosted server. `url` is the connection's domain and may lack a scheme. */
|
|
5
|
+
interface SelfHostedProvider {
|
|
6
|
+
provider: GitHostingProviderId;
|
|
7
|
+
url: string;
|
|
8
|
+
}
|
|
9
|
+
/** What the pull-request APIs need to address a repository. */
|
|
10
|
+
interface RemoteRepository {
|
|
11
|
+
provider: GitHostingProviderId;
|
|
12
|
+
/**
|
|
13
|
+
* `github`/`githubEnterprise`: the owner. `gitlab`/`gitlabSelfHosted`: the full namespace
|
|
14
|
+
* (`group/sub`). `bitbucket`: the workspace. `bitbucketServer`: the project key (uppercased
|
|
15
|
+
* unless it is a `~user` personal project). `azure`: the organization. `azureDevopsServer`:
|
|
16
|
+
* the collection name.
|
|
17
|
+
*/
|
|
18
|
+
owner: string;
|
|
19
|
+
/** The repository name (the slug on Bitbucket). */
|
|
20
|
+
name: string;
|
|
21
|
+
/** Set for `azure` and `azureDevopsServer`. */
|
|
22
|
+
azureProject?: string;
|
|
23
|
+
/** The lowercased hostname of the remote. */
|
|
24
|
+
host: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolves a git remote URL to the provider and repository coordinates the provider APIs need.
|
|
28
|
+
*
|
|
29
|
+
* Connected self-hosted servers (`selfHosted`) are matched first, by exact hostname in array order;
|
|
30
|
+
* then the cloud hosts by exact hostname. Never matches by substring, so `notgithub.example.com`
|
|
31
|
+
* is not GitHub. Supports `https`, `http`, `ssh`, `git`, `git+ssh` and `ssh+git` URLs and the scp form
|
|
32
|
+
* (`git@host:path`). Returns `undefined` when no provider owns the host or the path does not have
|
|
33
|
+
* the provider's layout.
|
|
34
|
+
*/
|
|
35
|
+
declare function parseRemoteUrl(remoteUrl: string, selfHosted?: readonly SelfHostedProvider[]): RemoteRepository | undefined;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { RemoteRepository, SelfHostedProvider, parseRemoteUrl };
|
package/dist/remotes.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isSelfHosted as e}from"./providers.js";const t={"github.com":`github`,"gitlab.com":`gitlab`,"bitbucket.org":`bitbucket`,"bitbucket.com":`bitbucket`,"dev.azure.com":`azure`,"ssh.dev.azure.com":`azure`},n=/^(?:https?|ssh|git|git\+ssh|ssh\+git):\/\//i,r=/^(?:[^@/\s]+@)?([^:/\s]+):(?!\/)(.*)$/;function i(t,n=[]){let r=a(t.trim());if(r!=null){for(let t of n){if(!e(t.provider))continue;let n=s(r,t);if(n!=null)return n}return c(r)}}function a(e){let t,i;if(n.test(e)){let n;try{n=new URL(e)}catch{return}t=n.hostname,i=n.pathname}else if(e.includes(`://`))return;else{let n=r.exec(e);if(n==null)return;t=n[1],i=n[2]}let a=o(i);if(!(t===``||a==null))return{host:t.toLowerCase(),segments:a}}function o(e){let t=e.replace(/^\/+|\/+$/g,``).replace(/\.git$/,``);if(t===``)return[];let n=[];for(let e of t.split(`/`)){let t;try{t=decodeURIComponent(e)}catch{return}if(t===``||t===`.`||t===`..`)return;n.push(t)}return n}function s(e,t){let n=a(/^[a-z][a-z0-9+.-]*:\/\//i.test(t.url)?t.url.trim():`https://${t.url.trim()}`);if(n==null||n.host!==e.host)return;let r=d(e.segments,n.segments);if(t.provider===`azureDevopsServer`){if(n.segments.length===0||!r)return;let t=f(e.segments.slice(n.segments.length));return t==null?void 0:{provider:`azureDevopsServer`,owner:n.segments[n.segments.length-1],name:t.repo,azureProject:t.project,host:e.host}}let i=r?e.segments.slice(n.segments.length):e.segments,o=l(t.provider,i);return o==null?void 0:{provider:t.provider,...o,host:e.host}}function c(e){let{host:n,segments:r}=e;if(n===`vs-ssh.visualstudio.com`)return h(n,m(r));if(/^[^.]+\.visualstudio\.com$/.test(n)){let e=n.slice(0,n.indexOf(`.`)),t=/^defaultcollection$/i.test(r[0]??``)?r.slice(1):void 0,i=(t==null?void 0:f(t))??f(r);return h(n,i&&{organization:e,...i})}let i=Object.hasOwn(t,n)?t[n]:void 0;if(i==null)return;if(i===`azure`)return h(n,n===`ssh.dev.azure.com`?m(r):p(r));let a=l(i,r);return a==null?void 0:{provider:i,...a,host:n}}function l(e,t){switch(e){case`gitlab`:case`gitlabSelfHosted`:return t.length<2?void 0:{owner:t.slice(0,-1).join(`/`),name:t[t.length-1]};case`bitbucketServer`:{let e=u(t[0]?.toLowerCase()===`scm`?t.slice(1):t);return e&&{owner:e.owner.startsWith(`~`)?e.owner:e.owner.toUpperCase(),name:e.name}}default:return u(t)}}function u(e){return e.length===2?{owner:e[0],name:e[1]}:void 0}function d(e,t){return t.length>0&&e.length>t.length&&t.every((t,n)=>t.toLowerCase()===e[n].toLowerCase())}function f(e){if(e.length===3&&e[1]===`_git`)return{project:e[0],repo:e[2]};if(e.length===2&&e[0]===`_git`)return{project:e[1],repo:e[1]}}function p(e){let[t,...n]=e,r=t==null?void 0:f(n);return r&&{organization:t,...r}}function m(e){if(!(e.length!==4||!/^v\d+$/i.test(e[0])))return{organization:e[1],project:e[2],repo:e[3]}}function h(e,t){if(t?.organization!=null)return{provider:`azure`,owner:t.organization,name:t.repo,azureProject:t.project,host:e}}export{i as parseRemoteUrl};
|
package/package.json
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitkraken/integrations-tools",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "GitKraken integrations: launchpad prioritization, git-hosting remote resolution, provider-token retrieval and pull-request creation for GitHub, GitLab, Bitbucket and Azure DevOps (cloud and self-hosted).",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"author": "Axosoft, LLC dba GitKraken",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">= 22"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"CHANGELOG.md"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@gitkraken/api-tools": "^0.3.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^22.0.0",
|
|
27
|
+
"typescript": "^6.0.2",
|
|
28
|
+
"vitest": "^4.1.1"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsdown --config-loader=unrun",
|
|
32
|
+
"build:dev": "tsdown --config tsdown.dev.config.ts --config-loader=unrun",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"test": "vitest run"
|
|
35
|
+
}
|
|
6
36
|
}
|