mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-11 20:05:25 +00:00
Add Bitbucket support (#275)
* [wip] add bitbucket schema * wip bitbucket support * add support for pulling bitbucket repos and UI support for bitbucket * fix bitbucket app password auth case * add back support for multiple workspaces and add exclude logic * add branches to bitbucket * add bitbucket server support * add docs for bitbucket and minor nits * doc nits * code rabbit fixes * fix build error * add bitbucket web ui support * misc cleanups and fix ui issues with bitbucket connections * add changelog entry
This commit is contained in:
parent
2acb1e558f
commit
b6dedc78ba
41 changed files with 2415 additions and 61 deletions
|
|
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- [Sourcebot EE] Added search contexts, user-defined groupings of repositories that help focus searches on specific areas of a codebase. [#273](https://github.com/sourcebot-dev/sourcebot/pull/273)
|
- [Sourcebot EE] Added search contexts, user-defined groupings of repositories that help focus searches on specific areas of a codebase. [#273](https://github.com/sourcebot-dev/sourcebot/pull/273)
|
||||||
|
- Added support for Bitbucket Cloud and Bitbucket Data Center connections. [#275](https://github.com/sourcebot-dev/sourcebot/pull/275)
|
||||||
|
|
||||||
|
|
||||||
## [3.0.4] - 2025-04-12
|
## [3.0.4] - 2025-04-12
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@
|
||||||
"docs/connections/overview",
|
"docs/connections/overview",
|
||||||
"docs/connections/github",
|
"docs/connections/github",
|
||||||
"docs/connections/gitlab",
|
"docs/connections/gitlab",
|
||||||
|
"docs/connections/bitbucket-cloud",
|
||||||
|
"docs/connections/bitbucket-data-center",
|
||||||
"docs/connections/gitea",
|
"docs/connections/gitea",
|
||||||
"docs/connections/gerrit",
|
"docs/connections/gerrit",
|
||||||
"docs/connections/request-new"
|
"docs/connections/request-new"
|
||||||
|
|
|
||||||
201
docs/docs/connections/bitbucket-cloud.mdx
Normal file
201
docs/docs/connections/bitbucket-cloud.mdx
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
---
|
||||||
|
title: Linking code from Bitbucket Cloud
|
||||||
|
sidebarTitle: Bitbucket Cloud
|
||||||
|
---
|
||||||
|
|
||||||
|
import BitbucketToken from '/snippets/bitbucket-token.mdx';
|
||||||
|
import BitbucketAppPassword from '/snippets/bitbucket-app-password.mdx';
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
<AccordionGroup>
|
||||||
|
<Accordion title="Sync individual repos">
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "bitbucket",
|
||||||
|
"deploymentType": "cloud",
|
||||||
|
"repos": [
|
||||||
|
"myWorkspace/myRepo"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="Sync all repos in a workspace">
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "bitbucket",
|
||||||
|
"deploymentType": "cloud",
|
||||||
|
"workspaces": [
|
||||||
|
"myWorkspace"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="Sync all repos in a project">
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "bitbucket",
|
||||||
|
"deploymentType": "cloud",
|
||||||
|
"projects": [
|
||||||
|
"myProject"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="Exclude repos from syncing">
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "bitbucket",
|
||||||
|
"deploymentType": "cloud",
|
||||||
|
// Include all repos in my-workspace...
|
||||||
|
"workspaces": [
|
||||||
|
"myWorkspace"
|
||||||
|
],
|
||||||
|
// ...except:
|
||||||
|
"exclude": {
|
||||||
|
// repos that are archived
|
||||||
|
"archived": true,
|
||||||
|
// repos that are forks
|
||||||
|
"forks": true,
|
||||||
|
// repos that match these glob patterns
|
||||||
|
"repos": [
|
||||||
|
"myWorkspace/repo1",
|
||||||
|
"myWorkspace2/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Accordion>
|
||||||
|
</AccordionGroup>
|
||||||
|
|
||||||
|
## Authenticating with Bitbucket Cloud
|
||||||
|
|
||||||
|
In order to index private repositories, you'll need to provide authentication credentials. You can do this using an `App Password` or an `Access Token`
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="App Password">
|
||||||
|
Navigate to the [app password creation page](https://bitbucket.org/account/settings/app-passwords/) and create an app password. Ensure that it has the proper permissions for the scope
|
||||||
|
of info you want to fetch (i.e. workspace, project, and/or repo level)
|
||||||
|

|
||||||
|
|
||||||
|
Next, provide your username + app password pair to Sourcebot:
|
||||||
|
|
||||||
|
<BitbucketAppPassword />
|
||||||
|
</Tab>
|
||||||
|
<Tab title="Access Token">
|
||||||
|
Create an access token for the desired scope (repo, project, or workspace). Visit the official [Bitbucket Cloud docs](https://support.atlassian.com/bitbucket-cloud/docs/access-tokens/)
|
||||||
|
for more info.
|
||||||
|
|
||||||
|
Next, provide the access token to Sourcebot:
|
||||||
|
|
||||||
|
<BitbucketToken />
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
|
||||||
|
## Schema reference
|
||||||
|
|
||||||
|
<Accordion title="Reference">
|
||||||
|
[schemas/v3/bitbucket.json](https://github.com/sourcebot-dev/sourcebot/blob/main/schemas/v3/bitbucket.json)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"title": "BitbucketConnectionConfig",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "bitbucket",
|
||||||
|
"description": "Bitbucket configuration"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The username to use for authentication. Only needed if token is an app password."
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"$ref": "./shared.json#/definitions/Token",
|
||||||
|
"description": "An authentication token.",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"secret": "SECRET_KEY"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "url",
|
||||||
|
"default": "https://api.bitbucket.org/2.0",
|
||||||
|
"description": "Bitbucket URL",
|
||||||
|
"examples": [
|
||||||
|
"https://bitbucket.example.com"
|
||||||
|
],
|
||||||
|
"pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$"
|
||||||
|
},
|
||||||
|
"deploymentType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["cloud", "server"],
|
||||||
|
"default": "cloud",
|
||||||
|
"description": "The type of Bitbucket deployment"
|
||||||
|
},
|
||||||
|
"workspaces": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of workspaces to sync. Ignored if deploymentType is server."
|
||||||
|
},
|
||||||
|
"projects": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of projects to sync"
|
||||||
|
},
|
||||||
|
"repos": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of repos to sync"
|
||||||
|
},
|
||||||
|
"exclude": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"archived": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Exclude archived repositories from syncing."
|
||||||
|
},
|
||||||
|
"forks": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Exclude forked repositories from syncing."
|
||||||
|
},
|
||||||
|
"repos": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
[
|
||||||
|
"cloud_workspace/repo1",
|
||||||
|
"server_project/repo2"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"description": "List of specific repos to exclude from syncing."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"revisions": {
|
||||||
|
"$ref": "./shared.json#/definitions/GitRevisions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
180
docs/docs/connections/bitbucket-data-center.mdx
Normal file
180
docs/docs/connections/bitbucket-data-center.mdx
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
---
|
||||||
|
title: Linking code from Bitbucket Data Center
|
||||||
|
sidebarTitle: Bitbucket Data Center
|
||||||
|
---
|
||||||
|
|
||||||
|
import BitbucketToken from '/snippets/bitbucket-token.mdx';
|
||||||
|
import BitbucketAppPassword from '/snippets/bitbucket-app-password.mdx';
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
<AccordionGroup>
|
||||||
|
<Accordion title="Sync individual repos">
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "bitbucket",
|
||||||
|
"deploymentType": "server",
|
||||||
|
"url": "https://mybitbucketdeployment.com",
|
||||||
|
"repos": [
|
||||||
|
"myProject/myRepo"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="Sync all repos in a project">
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "bitbucket",
|
||||||
|
"deploymentType": "server",
|
||||||
|
"url": "https://mybitbucketdeployment.com",
|
||||||
|
"projects": [
|
||||||
|
"myProject"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="Exclude repos from syncing">
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "bitbucket",
|
||||||
|
"deploymentType": "server",
|
||||||
|
"url": "https://mybitbucketdeployment.com",
|
||||||
|
// Include all repos in myProject...
|
||||||
|
"projects": [
|
||||||
|
"myProject"
|
||||||
|
],
|
||||||
|
// ...except:
|
||||||
|
"exclude": {
|
||||||
|
// repos that are archived
|
||||||
|
"archived": true,
|
||||||
|
// repos that are forks
|
||||||
|
"forks": true,
|
||||||
|
// repos that match these glob patterns
|
||||||
|
"repos": [
|
||||||
|
"myProject/repo1",
|
||||||
|
"myProject2/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Accordion>
|
||||||
|
</AccordionGroup>
|
||||||
|
|
||||||
|
## Authenticating with Bitbucket Data Center
|
||||||
|
|
||||||
|
In order to index private repositories, you'll need to provide an access token to Sourcebot.
|
||||||
|
|
||||||
|
Create an access token for the desired scope (repo, project, or workspace). Visit the official [Bitbucket Data Center docs](https://confluence.atlassian.com/bitbucketserver/http-access-tokens-939515499.html)
|
||||||
|
for more info.
|
||||||
|
|
||||||
|
Next, provide the access token to Sourcebot:
|
||||||
|
|
||||||
|
<BitbucketToken />
|
||||||
|
|
||||||
|
|
||||||
|
## Schema reference
|
||||||
|
|
||||||
|
<Accordion title="Reference">
|
||||||
|
[schemas/v3/bitbucket.json](https://github.com/sourcebot-dev/sourcebot/blob/main/schemas/v3/bitbucket.json)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"title": "BitbucketConnectionConfig",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "bitbucket",
|
||||||
|
"description": "Bitbucket configuration"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The username to use for authentication. Only needed if token is an app password."
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"$ref": "./shared.json#/definitions/Token",
|
||||||
|
"description": "An authentication token.",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"secret": "SECRET_KEY"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "url",
|
||||||
|
"default": "https://api.bitbucket.org/2.0",
|
||||||
|
"description": "Bitbucket URL",
|
||||||
|
"examples": [
|
||||||
|
"https://bitbucket.example.com"
|
||||||
|
],
|
||||||
|
"pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$"
|
||||||
|
},
|
||||||
|
"deploymentType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["cloud", "server"],
|
||||||
|
"default": "cloud",
|
||||||
|
"description": "The type of Bitbucket deployment"
|
||||||
|
},
|
||||||
|
"workspaces": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of workspaces to sync. Ignored if deploymentType is server."
|
||||||
|
},
|
||||||
|
"projects": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of projects to sync"
|
||||||
|
},
|
||||||
|
"repos": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of repos to sync"
|
||||||
|
},
|
||||||
|
"exclude": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"archived": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Exclude archived repositories from syncing."
|
||||||
|
},
|
||||||
|
"forks": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Exclude forked repositories from syncing."
|
||||||
|
},
|
||||||
|
"repos": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
[
|
||||||
|
"cloud_workspace/repo1",
|
||||||
|
"server_project/repo2"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"description": "List of specific repos to exclude from syncing."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"revisions": {
|
||||||
|
"$ref": "./shared.json#/definitions/GitRevisions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
|
@ -26,6 +26,8 @@ There are two ways to define connections:
|
||||||
<CardGroup cols={2}>
|
<CardGroup cols={2}>
|
||||||
<Card horizontal title="GitHub" icon="github" href="/docs/connections/github" />
|
<Card horizontal title="GitHub" icon="github" href="/docs/connections/github" />
|
||||||
<Card horizontal title="GitLab" icon="gitlab" href="/docs/connections/gitlab" />
|
<Card horizontal title="GitLab" icon="gitlab" href="/docs/connections/gitlab" />
|
||||||
|
<Card horizontal title="Bitbucket Cloud" icon="bitbucket" href="/docs/connections/bitbucket-cloud" />
|
||||||
|
<Card horizontal title="Bitbucket Data Center" icon="bitbucket" href="/docs/connections/bitbucket-data-center" />
|
||||||
<Card horizontal title="Gitea" href="/docs/connections/gitea" />
|
<Card horizontal title="Gitea" href="/docs/connections/gitea" />
|
||||||
<Card horizontal title="Gerrit" href="/docs/connections/gerrit" />
|
<Card horizontal title="Gerrit" href="/docs/connections/gerrit" />
|
||||||
</CardGroup>
|
</CardGroup>
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
title: "Overview"
|
title: "Overview"
|
||||||
---
|
---
|
||||||
|
|
||||||
import ConnectionCards from '/snippets/connection-cards.mdx';
|
|
||||||
|
|
||||||
Sourcebot is an **[open-source](https://github.com/sourcebot-dev/sourcebot) code search tool** that is purpose built to search multi-million line codebases in seconds. It integrates with [GitHub](/docs/connections/github), [GitLab](/docs/connections/gitlab), and [other platforms](/docs/connections).
|
Sourcebot is an **[open-source](https://github.com/sourcebot-dev/sourcebot) code search tool** that is purpose built to search multi-million line codebases in seconds. It integrates with [GitHub](/docs/connections/github), [GitLab](/docs/connections/gitlab), and [other platforms](/docs/connections).
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
|
||||||
BIN
docs/images/bitbucket_app_password_perms.png
Normal file
BIN
docs/images/bitbucket_app_password_perms.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 140 KiB |
|
|
@ -76,8 +76,10 @@ Sourcebot is open source and can be self-hosted using our official [Docker image
|
||||||
Sourcebot supports indexing public & private code on the following code hosts:
|
Sourcebot supports indexing public & private code on the following code hosts:
|
||||||
|
|
||||||
<CardGroup cols={2}>
|
<CardGroup cols={2}>
|
||||||
<Card horizontal title="GitHub" href="/docs/connections/github" />
|
<Card horizontal title="GitHub" icon="github" href="/docs/connections/github" />
|
||||||
<Card horizontal title="GitLab" href="/docs/connections/gitlab" />
|
<Card horizontal title="GitLab" icon="gitlab" href="/docs/connections/gitlab" />
|
||||||
|
<Card horizontal title="Bitbucket Cloud" icon="bitbucket" href="/docs/connections/bitbucket-cloud" />
|
||||||
|
<Card horizontal title="Bitbucket Data Center" icon="bitbucket" href="/docs/connections/bitbucket-data-center" />
|
||||||
<Card horizontal title="Gitea" href="/docs/connections/gitea" />
|
<Card horizontal title="Gitea" href="/docs/connections/gitea" />
|
||||||
<Card horizontal title="Gerrit" href="/docs/connections/gerrit" />
|
<Card horizontal title="Gerrit" href="/docs/connections/gerrit" />
|
||||||
</CardGroup>
|
</CardGroup>
|
||||||
|
|
|
||||||
51
docs/snippets/bitbucket-app-password.mdx
Normal file
51
docs/snippets/bitbucket-app-password.mdx
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Environment Variable">
|
||||||
|
<Note>Environment variables are only supported in a [declarative config](/self-hosting/more/declarative-config) and cannot be used in the web UI.</Note>
|
||||||
|
|
||||||
|
1. Add the `token` and `user` (username associated with the app password you created) properties to your connection config:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "bitbucket",
|
||||||
|
"deploymentType": "cloud",
|
||||||
|
"user": "myusername",
|
||||||
|
"token": {
|
||||||
|
// note: this env var can be named anything. It
|
||||||
|
// doesn't need to be `BITBUCKET_TOKEN`.
|
||||||
|
"env": "BITBUCKET_TOKEN"
|
||||||
|
}
|
||||||
|
// .. rest of config ..
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Pass this environment variable each time you run Sourcebot:
|
||||||
|
```bash
|
||||||
|
docker run \
|
||||||
|
-e BITBUCKET_TOKEN=<PAT> \
|
||||||
|
/* additional args */ \
|
||||||
|
ghcr.io/sourcebot-dev/sourcebot:latest
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab title="Secret">
|
||||||
|
<Note>Secrets are only supported when [authentication](/self-hosting/more/authentication) is enabled.</Note>
|
||||||
|
|
||||||
|
1. Navigate to **Secrets** in settings and create a new secret with your access token:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
2. Add the `token` and `user` (username associated with the app password you created) properties to your connection config:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "bitbucket",
|
||||||
|
"deploymentType": "cloud",
|
||||||
|
"user": "myusername",
|
||||||
|
"token": {
|
||||||
|
"secret": "mysecret"
|
||||||
|
}
|
||||||
|
// .. rest of config ..
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
47
docs/snippets/bitbucket-token.mdx
Normal file
47
docs/snippets/bitbucket-token.mdx
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Environment Variable">
|
||||||
|
<Note>Environment variables are only supported in a [declarative config](/self-hosting/more/declarative-config) and cannot be used in the web UI.</Note>
|
||||||
|
|
||||||
|
1. Add the `token` property to your connection config:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "bitbucket",
|
||||||
|
"token": {
|
||||||
|
// note: this env var can be named anything. It
|
||||||
|
// doesn't need to be `BITBUCKET_TOKEN`.
|
||||||
|
"env": "BITBUCKET_TOKEN"
|
||||||
|
}
|
||||||
|
// .. rest of config ..
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Pass this environment variable each time you run Sourcebot:
|
||||||
|
```bash
|
||||||
|
docker run \
|
||||||
|
-e BITBUCKET_TOKEN=<PAT> \
|
||||||
|
/* additional args */ \
|
||||||
|
ghcr.io/sourcebot-dev/sourcebot:latest
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab title="Secret">
|
||||||
|
<Note>Secrets are only supported when [authentication](/self-hosting/more/authentication) is enabled.</Note>
|
||||||
|
|
||||||
|
1. Navigate to **Secrets** in settings and create a new secret with your PAT:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
2. Add the `token` property to your connection config:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "bitbucket",
|
||||||
|
"token": {
|
||||||
|
"secret": "mysecret"
|
||||||
|
}
|
||||||
|
// .. rest of config ..
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
<CardGroup cols={2}>
|
|
||||||
<Card title="GitHub" icon="github" href="/docs/connections/github"></Card>
|
|
||||||
<Card title="GitLab" icon="gitlab" href="/docs/connections/gitlab"></Card>
|
|
||||||
</CardGroup>
|
|
||||||
|
|
@ -20,5 +20,8 @@
|
||||||
"dotenv-cli": "^8.0.0",
|
"dotenv-cli": "^8.0.0",
|
||||||
"npm-run-all": "^4.1.5"
|
"npm-run-all": "^4.1.5"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.7.0"
|
"packageManager": "yarn@4.7.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@coderabbitai/bitbucket": "^1.1.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
553
packages/backend/src/bitbucket.ts
Normal file
553
packages/backend/src/bitbucket.ts
Normal file
|
|
@ -0,0 +1,553 @@
|
||||||
|
import { createBitbucketCloudClient } from "@coderabbitai/bitbucket/cloud";
|
||||||
|
import { createBitbucketServerClient } from "@coderabbitai/bitbucket/server";
|
||||||
|
import { BitbucketConnectionConfig } from "@sourcebot/schemas/v3/bitbucket.type";
|
||||||
|
import type { ClientOptions, ClientPathsWithMethod } from "openapi-fetch";
|
||||||
|
import { createLogger } from "./logger.js";
|
||||||
|
import { PrismaClient } from "@sourcebot/db";
|
||||||
|
import { getTokenFromConfig, measure, fetchWithRetry } from "./utils.js";
|
||||||
|
import * as Sentry from "@sentry/node";
|
||||||
|
import {
|
||||||
|
SchemaRepository as CloudRepository,
|
||||||
|
} from "@coderabbitai/bitbucket/cloud/openapi";
|
||||||
|
import { SchemaRestRepository as ServerRepository } from "@coderabbitai/bitbucket/server/openapi";
|
||||||
|
import { processPromiseResults } from "./connectionUtils.js";
|
||||||
|
import { throwIfAnyFailed } from "./connectionUtils.js";
|
||||||
|
|
||||||
|
const logger = createLogger("Bitbucket");
|
||||||
|
const BITBUCKET_CLOUD_GIT = 'https://bitbucket.org';
|
||||||
|
const BITBUCKET_CLOUD_API = 'https://api.bitbucket.org/2.0';
|
||||||
|
const BITBUCKET_CLOUD = "cloud";
|
||||||
|
const BITBUCKET_SERVER = "server";
|
||||||
|
|
||||||
|
export type BitbucketRepository = CloudRepository | ServerRepository;
|
||||||
|
|
||||||
|
interface BitbucketClient {
|
||||||
|
deploymentType: string;
|
||||||
|
token: string | undefined;
|
||||||
|
apiClient: any;
|
||||||
|
baseUrl: string;
|
||||||
|
gitUrl: string;
|
||||||
|
getReposForWorkspace: (client: BitbucketClient, workspaces: string[]) => Promise<{validRepos: BitbucketRepository[], notFoundWorkspaces: string[]}>;
|
||||||
|
getReposForProjects: (client: BitbucketClient, projects: string[]) => Promise<{validRepos: BitbucketRepository[], notFoundProjects: string[]}>;
|
||||||
|
getRepos: (client: BitbucketClient, repos: string[]) => Promise<{validRepos: BitbucketRepository[], notFoundRepos: string[]}>;
|
||||||
|
shouldExcludeRepo: (repo: BitbucketRepository, config: BitbucketConnectionConfig) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloudAPI = ReturnType<typeof createBitbucketCloudClient>;
|
||||||
|
type CloudGetRequestPath = ClientPathsWithMethod<CloudAPI, "get">;
|
||||||
|
|
||||||
|
type ServerAPI = ReturnType<typeof createBitbucketServerClient>;
|
||||||
|
type ServerGetRequestPath = ClientPathsWithMethod<ServerAPI, "get">;
|
||||||
|
|
||||||
|
type CloudPaginatedResponse<T> = {
|
||||||
|
readonly next?: string;
|
||||||
|
readonly page?: number;
|
||||||
|
readonly pagelen?: number;
|
||||||
|
readonly previous?: string;
|
||||||
|
readonly size?: number;
|
||||||
|
readonly values?: readonly T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerPaginatedResponse<T> = {
|
||||||
|
readonly size: number;
|
||||||
|
readonly limit: number;
|
||||||
|
readonly isLastPage: boolean;
|
||||||
|
readonly values: readonly T[];
|
||||||
|
readonly start: number;
|
||||||
|
readonly nextPageStart: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getBitbucketReposFromConfig = async (config: BitbucketConnectionConfig, orgId: number, db: PrismaClient) => {
|
||||||
|
const token = config.token ?
|
||||||
|
await getTokenFromConfig(config.token, orgId, db, logger) :
|
||||||
|
undefined;
|
||||||
|
|
||||||
|
if (config.deploymentType === 'server' && !config.url) {
|
||||||
|
throw new Error('URL is required for Bitbucket Server');
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = config.deploymentType === 'server' ?
|
||||||
|
serverClient(config.url!, config.user, token) :
|
||||||
|
cloudClient(config.user, token);
|
||||||
|
|
||||||
|
let allRepos: BitbucketRepository[] = [];
|
||||||
|
let notFound: {
|
||||||
|
orgs: string[],
|
||||||
|
users: string[],
|
||||||
|
repos: string[],
|
||||||
|
} = {
|
||||||
|
orgs: [],
|
||||||
|
users: [],
|
||||||
|
repos: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (config.workspaces) {
|
||||||
|
const { validRepos, notFoundWorkspaces } = await client.getReposForWorkspace(client, config.workspaces);
|
||||||
|
allRepos = allRepos.concat(validRepos);
|
||||||
|
notFound.orgs = notFoundWorkspaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.projects) {
|
||||||
|
const { validRepos, notFoundProjects } = await client.getReposForProjects(client, config.projects);
|
||||||
|
allRepos = allRepos.concat(validRepos);
|
||||||
|
notFound.orgs = notFoundProjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.repos) {
|
||||||
|
const { validRepos, notFoundRepos } = await client.getRepos(client, config.repos);
|
||||||
|
allRepos = allRepos.concat(validRepos);
|
||||||
|
notFound.repos = notFoundRepos;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredRepos = allRepos.filter((repo) => {
|
||||||
|
return !client.shouldExcludeRepo(repo, config);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
validRepos: filteredRepos,
|
||||||
|
notFound,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloudClient(user: string | undefined, token: string | undefined): BitbucketClient {
|
||||||
|
|
||||||
|
const authorizationString =
|
||||||
|
token
|
||||||
|
? !user || user == "x-token-auth"
|
||||||
|
? `Bearer ${token}`
|
||||||
|
: `Basic ${Buffer.from(`${user}:${token}`).toString('base64')}`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const clientOptions: ClientOptions = {
|
||||||
|
baseUrl: BITBUCKET_CLOUD_API,
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
...(authorizationString ? { Authorization: authorizationString } : {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const apiClient = createBitbucketCloudClient(clientOptions);
|
||||||
|
var client: BitbucketClient = {
|
||||||
|
deploymentType: BITBUCKET_CLOUD,
|
||||||
|
token: token,
|
||||||
|
apiClient: apiClient,
|
||||||
|
baseUrl: BITBUCKET_CLOUD_API,
|
||||||
|
gitUrl: BITBUCKET_CLOUD_GIT,
|
||||||
|
getReposForWorkspace: cloudGetReposForWorkspace,
|
||||||
|
getReposForProjects: cloudGetReposForProjects,
|
||||||
|
getRepos: cloudGetRepos,
|
||||||
|
shouldExcludeRepo: cloudShouldExcludeRepo,
|
||||||
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need to do `V extends CloudGetRequestPath` since we will need to call `apiClient.GET(url, ...)`, which
|
||||||
|
* expects `url` to be of type `CloudGetRequestPath`. See example.
|
||||||
|
**/
|
||||||
|
const getPaginatedCloud = async <T>(
|
||||||
|
path: CloudGetRequestPath,
|
||||||
|
get: (url: CloudGetRequestPath) => Promise<CloudPaginatedResponse<T>>
|
||||||
|
): Promise<T[]> => {
|
||||||
|
const results: T[] = [];
|
||||||
|
let url = path;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const response = await get(url);
|
||||||
|
|
||||||
|
if (!response.values || response.values.length === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.push(...response.values);
|
||||||
|
|
||||||
|
if (!response.next) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
url = response.next as CloudGetRequestPath;
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function cloudGetReposForWorkspace(client: BitbucketClient, workspaces: string[]): Promise<{validRepos: CloudRepository[], notFoundWorkspaces: string[]}> {
|
||||||
|
const results = await Promise.allSettled(workspaces.map(async (workspace) => {
|
||||||
|
try {
|
||||||
|
logger.debug(`Fetching all repos for workspace ${workspace}...`);
|
||||||
|
|
||||||
|
const path = `/repositories/${workspace}` as CloudGetRequestPath;
|
||||||
|
const { durationMs, data } = await measure(async () => {
|
||||||
|
const fetchFn = () => getPaginatedCloud<CloudRepository>(path, async (url) => {
|
||||||
|
const response = await client.apiClient.GET(url, {
|
||||||
|
params: {
|
||||||
|
path: {
|
||||||
|
workspace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const { data, error } = response;
|
||||||
|
if (error) {
|
||||||
|
throw new Error(`Failed to fetch projects for workspace ${workspace}: ${JSON.stringify(error)}`);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
return fetchWithRetry(fetchFn, `workspace ${workspace}`, logger);
|
||||||
|
});
|
||||||
|
logger.debug(`Found ${data.length} repos for workspace ${workspace} in ${durationMs}ms.`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'valid' as const,
|
||||||
|
data: data,
|
||||||
|
};
|
||||||
|
} catch (e: any) {
|
||||||
|
Sentry.captureException(e);
|
||||||
|
logger.error(`Failed to get repos for workspace ${workspace}: ${e}`);
|
||||||
|
|
||||||
|
const status = e?.cause?.response?.status;
|
||||||
|
if (status == 404) {
|
||||||
|
logger.error(`Workspace ${workspace} not found or invalid access`)
|
||||||
|
return {
|
||||||
|
type: 'notFound' as const,
|
||||||
|
value: workspace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
throwIfAnyFailed(results);
|
||||||
|
const { validItems: validRepos, notFoundItems: notFoundWorkspaces } = processPromiseResults(results);
|
||||||
|
return {
|
||||||
|
validRepos,
|
||||||
|
notFoundWorkspaces,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cloudGetReposForProjects(client: BitbucketClient, projects: string[]): Promise<{validRepos: CloudRepository[], notFoundProjects: string[]}> {
|
||||||
|
const results = await Promise.allSettled(projects.map(async (project) => {
|
||||||
|
const [workspace, project_name] = project.split('/');
|
||||||
|
if (!workspace || !project_name) {
|
||||||
|
logger.error(`Invalid project ${project}`);
|
||||||
|
return {
|
||||||
|
type: 'notFound' as const,
|
||||||
|
value: project
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`Fetching all repos for project ${project} for workspace ${workspace}...`);
|
||||||
|
try {
|
||||||
|
const path = `/repositories/${workspace}` as CloudGetRequestPath;
|
||||||
|
const repos = await getPaginatedCloud<CloudRepository>(path, async (url) => {
|
||||||
|
const response = await client.apiClient.GET(url, {
|
||||||
|
params: {
|
||||||
|
query: {
|
||||||
|
q: `project.key="${project_name}"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const { data, error } = response;
|
||||||
|
if (error) {
|
||||||
|
throw new Error (`Failed to fetch projects for workspace ${workspace}: ${error.type}`);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug(`Found ${repos.length} repos for project ${project_name} for workspace ${workspace}.`);
|
||||||
|
return {
|
||||||
|
type: 'valid' as const,
|
||||||
|
data: repos
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
Sentry.captureException(e);
|
||||||
|
logger.error(`Failed to fetch repos for project ${project_name}: ${e}`);
|
||||||
|
|
||||||
|
const status = e?.cause?.response?.status;
|
||||||
|
if (status == 404) {
|
||||||
|
logger.error(`Project ${project_name} not found in ${workspace} or invalid access`)
|
||||||
|
return {
|
||||||
|
type: 'notFound' as const,
|
||||||
|
value: project
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
throwIfAnyFailed(results);
|
||||||
|
const { validItems: validRepos, notFoundItems: notFoundProjects } = processPromiseResults(results);
|
||||||
|
return {
|
||||||
|
validRepos,
|
||||||
|
notFoundProjects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cloudGetRepos(client: BitbucketClient, repos: string[]): Promise<{validRepos: CloudRepository[], notFoundRepos: string[]}> {
|
||||||
|
const results = await Promise.allSettled(repos.map(async (repo) => {
|
||||||
|
const [workspace, repo_slug] = repo.split('/');
|
||||||
|
if (!workspace || !repo_slug) {
|
||||||
|
logger.error(`Invalid repo ${repo}`);
|
||||||
|
return {
|
||||||
|
type: 'notFound' as const,
|
||||||
|
value: repo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`Fetching repo ${repo_slug} for workspace ${workspace}...`);
|
||||||
|
try {
|
||||||
|
const path = `/repositories/${workspace}/${repo_slug}` as CloudGetRequestPath;
|
||||||
|
const response = await client.apiClient.GET(path);
|
||||||
|
const { data, error } = response;
|
||||||
|
if (error) {
|
||||||
|
throw new Error(`Failed to fetch repo ${repo}: ${error.type}`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 'valid' as const,
|
||||||
|
data: [data]
|
||||||
|
};
|
||||||
|
} catch (e: any) {
|
||||||
|
Sentry.captureException(e);
|
||||||
|
logger.error(`Failed to fetch repo ${repo}: ${e}`);
|
||||||
|
|
||||||
|
const status = e?.cause?.response?.status;
|
||||||
|
if (status === 404) {
|
||||||
|
logger.error(`Repo ${repo} not found in ${workspace} or invalid access`);
|
||||||
|
return {
|
||||||
|
type: 'notFound' as const,
|
||||||
|
value: repo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
throwIfAnyFailed(results);
|
||||||
|
const { validItems: validRepos, notFoundItems: notFoundRepos } = processPromiseResults(results);
|
||||||
|
return {
|
||||||
|
validRepos,
|
||||||
|
notFoundRepos
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloudShouldExcludeRepo(repo: BitbucketRepository, config: BitbucketConnectionConfig): boolean {
|
||||||
|
const cloudRepo = repo as CloudRepository;
|
||||||
|
|
||||||
|
const shouldExclude = (() => {
|
||||||
|
if (config.exclude?.repos && config.exclude.repos.includes(cloudRepo.full_name!)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!config.exclude?.archived) {
|
||||||
|
logger.warn(`Exclude archived repos flag provided in config but Bitbucket Cloud does not support archived repos. Ignoring...`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!config.exclude?.forks && cloudRepo.parent !== undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (shouldExclude) {
|
||||||
|
logger.debug(`Excluding repo ${cloudRepo.full_name} because it matches the exclude pattern`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function serverClient(url: string, user: string | undefined, token: string | undefined): BitbucketClient {
|
||||||
|
const authorizationString = (() => {
|
||||||
|
// If we're not given any credentials we return an empty auth string. This will only work if the project/repos are public
|
||||||
|
if(!user && !token) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// A user must be provided when using basic auth
|
||||||
|
// https://developer.atlassian.com/server/bitbucket/rest/v906/intro/#authentication
|
||||||
|
if (!user || user == "x-token-auth") {
|
||||||
|
return `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
return `Basic ${Buffer.from(`${user}:${token}`).toString('base64')}`;
|
||||||
|
})();
|
||||||
|
const clientOptions: ClientOptions = {
|
||||||
|
baseUrl: url,
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
Authorization: authorizationString,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const apiClient = createBitbucketServerClient(clientOptions);
|
||||||
|
var client: BitbucketClient = {
|
||||||
|
deploymentType: BITBUCKET_SERVER,
|
||||||
|
token: token,
|
||||||
|
apiClient: apiClient,
|
||||||
|
baseUrl: url,
|
||||||
|
gitUrl: url,
|
||||||
|
getReposForWorkspace: serverGetReposForWorkspace,
|
||||||
|
getReposForProjects: serverGetReposForProjects,
|
||||||
|
getRepos: serverGetRepos,
|
||||||
|
shouldExcludeRepo: serverShouldExcludeRepo,
|
||||||
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPaginatedServer = async <T>(
|
||||||
|
path: ServerGetRequestPath,
|
||||||
|
get: (url: ServerGetRequestPath, start?: number) => Promise<ServerPaginatedResponse<T>>
|
||||||
|
): Promise<T[]> => {
|
||||||
|
const results: T[] = [];
|
||||||
|
let nextStart: number | undefined;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const response = await get(path, nextStart);
|
||||||
|
|
||||||
|
if (!response.values || response.values.length === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.push(...response.values);
|
||||||
|
|
||||||
|
if (response.isLastPage) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextStart = response.nextPageStart;
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function serverGetReposForWorkspace(client: BitbucketClient, workspaces: string[]): Promise<{validRepos: ServerRepository[], notFoundWorkspaces: string[]}> {
|
||||||
|
logger.debug('Workspaces are not supported in Bitbucket Server');
|
||||||
|
return {
|
||||||
|
validRepos: [],
|
||||||
|
notFoundWorkspaces: workspaces
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function serverGetReposForProjects(client: BitbucketClient, projects: string[]): Promise<{validRepos: ServerRepository[], notFoundProjects: string[]}> {
|
||||||
|
const results = await Promise.allSettled(projects.map(async (project) => {
|
||||||
|
try {
|
||||||
|
logger.debug(`Fetching all repos for project ${project}...`);
|
||||||
|
|
||||||
|
const path = `/rest/api/1.0/projects/${project}/repos` as ServerGetRequestPath;
|
||||||
|
const { durationMs, data } = await measure(async () => {
|
||||||
|
const fetchFn = () => getPaginatedServer<ServerRepository>(path, async (url, start) => {
|
||||||
|
const response = await client.apiClient.GET(url, {
|
||||||
|
params: {
|
||||||
|
query: {
|
||||||
|
start,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const { data, error } = response;
|
||||||
|
if (error) {
|
||||||
|
throw new Error(`Failed to fetch repos for project ${project}: ${JSON.stringify(error)}`);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
return fetchWithRetry(fetchFn, `project ${project}`, logger);
|
||||||
|
});
|
||||||
|
logger.debug(`Found ${data.length} repos for project ${project} in ${durationMs}ms.`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'valid' as const,
|
||||||
|
data: data,
|
||||||
|
};
|
||||||
|
} catch (e: any) {
|
||||||
|
Sentry.captureException(e);
|
||||||
|
logger.error(`Failed to get repos for project ${project}: ${e}`);
|
||||||
|
|
||||||
|
const status = e?.cause?.response?.status;
|
||||||
|
if (status == 404) {
|
||||||
|
logger.error(`Project ${project} not found or invalid access`);
|
||||||
|
return {
|
||||||
|
type: 'notFound' as const,
|
||||||
|
value: project
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
throwIfAnyFailed(results);
|
||||||
|
const { validItems: validRepos, notFoundItems: notFoundProjects } = processPromiseResults(results);
|
||||||
|
return {
|
||||||
|
validRepos,
|
||||||
|
notFoundProjects
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function serverGetRepos(client: BitbucketClient, repos: string[]): Promise<{validRepos: ServerRepository[], notFoundRepos: string[]}> {
|
||||||
|
const results = await Promise.allSettled(repos.map(async (repo) => {
|
||||||
|
const [project, repo_slug] = repo.split('/');
|
||||||
|
if (!project || !repo_slug) {
|
||||||
|
logger.error(`Invalid repo ${repo}`);
|
||||||
|
return {
|
||||||
|
type: 'notFound' as const,
|
||||||
|
value: repo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`Fetching repo ${repo_slug} for project ${project}...`);
|
||||||
|
try {
|
||||||
|
const path = `/rest/api/1.0/projects/${project}/repos/${repo_slug}` as ServerGetRequestPath;
|
||||||
|
const response = await client.apiClient.GET(path);
|
||||||
|
const { data, error } = response;
|
||||||
|
if (error) {
|
||||||
|
throw new Error(`Failed to fetch repo ${repo}: ${error.type}`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 'valid' as const,
|
||||||
|
data: [data]
|
||||||
|
};
|
||||||
|
} catch (e: any) {
|
||||||
|
Sentry.captureException(e);
|
||||||
|
logger.error(`Failed to fetch repo ${repo}: ${e}`);
|
||||||
|
|
||||||
|
const status = e?.cause?.response?.status;
|
||||||
|
if (status === 404) {
|
||||||
|
logger.error(`Repo ${repo} not found in project ${project} or invalid access`);
|
||||||
|
return {
|
||||||
|
type: 'notFound' as const,
|
||||||
|
value: repo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
throwIfAnyFailed(results);
|
||||||
|
const { validItems: validRepos, notFoundItems: notFoundRepos } = processPromiseResults(results);
|
||||||
|
return {
|
||||||
|
validRepos,
|
||||||
|
notFoundRepos
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function serverShouldExcludeRepo(repo: BitbucketRepository, config: BitbucketConnectionConfig): boolean {
|
||||||
|
const serverRepo = repo as ServerRepository;
|
||||||
|
|
||||||
|
const projectName = serverRepo.project!.key;
|
||||||
|
const repoSlug = serverRepo.slug!;
|
||||||
|
|
||||||
|
const shouldExclude = (() => {
|
||||||
|
if (config.exclude?.repos && config.exclude.repos.includes(`${projectName}/${repoSlug}`)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!config.exclude?.archived && serverRepo.archived) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!config.exclude?.forks && serverRepo.origin !== undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (shouldExclude) {
|
||||||
|
logger.debug(`Excluding repo ${projectName}/${repoSlug} because it matches the exclude pattern`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ import { Settings } from "./types.js";
|
||||||
import { ConnectionConfig } from "@sourcebot/schemas/v3/connection.type";
|
import { ConnectionConfig } from "@sourcebot/schemas/v3/connection.type";
|
||||||
import { createLogger } from "./logger.js";
|
import { createLogger } from "./logger.js";
|
||||||
import { Redis } from 'ioredis';
|
import { Redis } from 'ioredis';
|
||||||
import { RepoData, compileGithubConfig, compileGitlabConfig, compileGiteaConfig, compileGerritConfig } from "./repoCompileUtils.js";
|
import { RepoData, compileGithubConfig, compileGitlabConfig, compileGiteaConfig, compileGerritConfig, compileBitbucketConfig } from "./repoCompileUtils.js";
|
||||||
import { BackendError, BackendException } from "@sourcebot/error";
|
import { BackendError, BackendException } from "@sourcebot/error";
|
||||||
import { captureEvent } from "./posthog.js";
|
import { captureEvent } from "./posthog.js";
|
||||||
import { env } from "./env.js";
|
import { env } from "./env.js";
|
||||||
|
|
@ -170,6 +170,9 @@ export class ConnectionManager implements IConnectionManager {
|
||||||
case 'gerrit': {
|
case 'gerrit': {
|
||||||
return await compileGerritConfig(config, job.data.connectionId, orgId);
|
return await compileGerritConfig(config, job.data.connectionId, orgId);
|
||||||
}
|
}
|
||||||
|
case 'bitbucket': {
|
||||||
|
return await compileBitbucketConfig(config, job.data.connectionId, orgId, this.db);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,14 @@ import { getGitHubReposFromConfig } from "./github.js";
|
||||||
import { getGitLabReposFromConfig } from "./gitlab.js";
|
import { getGitLabReposFromConfig } from "./gitlab.js";
|
||||||
import { getGiteaReposFromConfig } from "./gitea.js";
|
import { getGiteaReposFromConfig } from "./gitea.js";
|
||||||
import { getGerritReposFromConfig } from "./gerrit.js";
|
import { getGerritReposFromConfig } from "./gerrit.js";
|
||||||
|
import { BitbucketRepository, getBitbucketReposFromConfig } from "./bitbucket.js";
|
||||||
|
import { SchemaRestRepository as BitbucketServerRepository } from "@coderabbitai/bitbucket/server/openapi";
|
||||||
|
import { SchemaRepository as BitbucketCloudRepository } from "@coderabbitai/bitbucket/cloud/openapi";
|
||||||
import { Prisma, PrismaClient } from '@sourcebot/db';
|
import { Prisma, PrismaClient } from '@sourcebot/db';
|
||||||
import { WithRequired } from "./types.js"
|
import { WithRequired } from "./types.js"
|
||||||
import { marshalBool } from "./utils.js";
|
import { marshalBool } from "./utils.js";
|
||||||
import { createLogger } from './logger.js';
|
import { createLogger } from './logger.js';
|
||||||
import { GerritConnectionConfig, GiteaConnectionConfig, GitlabConnectionConfig } from '@sourcebot/schemas/v3/connection.type';
|
import { BitbucketConnectionConfig, GerritConnectionConfig, GiteaConnectionConfig, GitlabConnectionConfig } from '@sourcebot/schemas/v3/connection.type';
|
||||||
import { RepoMetadata } from './types.js';
|
import { RepoMetadata } from './types.js';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
|
|
@ -312,4 +315,120 @@ export const compileGerritConfig = async (
|
||||||
repos: [],
|
repos: [],
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const compileBitbucketConfig = async (
|
||||||
|
config: BitbucketConnectionConfig,
|
||||||
|
connectionId: number,
|
||||||
|
orgId: number,
|
||||||
|
db: PrismaClient) => {
|
||||||
|
|
||||||
|
const bitbucketReposResult = await getBitbucketReposFromConfig(config, orgId, db);
|
||||||
|
const bitbucketRepos = bitbucketReposResult.validRepos;
|
||||||
|
const notFound = bitbucketReposResult.notFound;
|
||||||
|
|
||||||
|
const hostUrl = config.url ?? 'https://bitbucket.org';
|
||||||
|
const repoNameRoot = new URL(hostUrl)
|
||||||
|
.toString()
|
||||||
|
.replace(/^https?:\/\//, '');
|
||||||
|
|
||||||
|
const getCloneUrl = (repo: BitbucketRepository) => {
|
||||||
|
if (!repo.links) {
|
||||||
|
throw new Error(`No clone links found for server repo ${repo.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the cloud case we simply fetch the html link and use that as the clone url. For server we
|
||||||
|
// need to fetch the actual clone url
|
||||||
|
if (config.deploymentType === 'cloud') {
|
||||||
|
const htmlLink = repo.links.html as { href: string };
|
||||||
|
return htmlLink.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloneLinks = repo.links.clone as {
|
||||||
|
href: string;
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
for (const link of cloneLinks) {
|
||||||
|
if (link.name === 'http') {
|
||||||
|
return link.href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`No clone links found for repo ${repo.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getWebUrl = (repo: BitbucketRepository) => {
|
||||||
|
const isServer = config.deploymentType === 'server';
|
||||||
|
const repoLinks = (repo as BitbucketServerRepository | BitbucketCloudRepository).links;
|
||||||
|
const repoName = isServer ? (repo as BitbucketServerRepository).name : (repo as BitbucketCloudRepository).full_name;
|
||||||
|
|
||||||
|
if (!repoLinks) {
|
||||||
|
throw new Error(`No links found for ${isServer ? 'server' : 'cloud'} repo ${repoName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In server case we get an array of lenth == 1 links in the self field, while in cloud case we get a single
|
||||||
|
// link object in the html field
|
||||||
|
const link = isServer ? (repoLinks.self as { name: string, href: string }[])?.[0] : repoLinks.html as { href: string };
|
||||||
|
if (!link || !link.href) {
|
||||||
|
throw new Error(`No ${isServer ? 'self' : 'html'} link found for ${isServer ? 'server' : 'cloud'} repo ${repoName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return link.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
const repos = bitbucketRepos.map((repo) => {
|
||||||
|
const isServer = config.deploymentType === 'server';
|
||||||
|
const codeHostType = isServer ? 'bitbucket-server' : 'bitbucket-cloud'; // zoekt expects bitbucket-server
|
||||||
|
const displayName = isServer ? (repo as BitbucketServerRepository).name! : (repo as BitbucketCloudRepository).full_name!;
|
||||||
|
const externalId = isServer ? (repo as BitbucketServerRepository).id!.toString() : (repo as BitbucketCloudRepository).uuid!;
|
||||||
|
const isPublic = isServer ? (repo as BitbucketServerRepository).public : (repo as BitbucketCloudRepository).is_private === false;
|
||||||
|
const isArchived = isServer ? (repo as BitbucketServerRepository).archived === true : false;
|
||||||
|
const isFork = isServer ? (repo as BitbucketServerRepository).origin !== undefined : (repo as BitbucketCloudRepository).parent !== undefined;
|
||||||
|
const repoName = path.join(repoNameRoot, displayName);
|
||||||
|
const cloneUrl = getCloneUrl(repo);
|
||||||
|
const webUrl = getWebUrl(repo);
|
||||||
|
|
||||||
|
const record: RepoData = {
|
||||||
|
external_id: externalId,
|
||||||
|
external_codeHostType: codeHostType,
|
||||||
|
external_codeHostUrl: hostUrl,
|
||||||
|
cloneUrl: cloneUrl,
|
||||||
|
webUrl: webUrl,
|
||||||
|
name: repoName,
|
||||||
|
displayName: displayName,
|
||||||
|
isFork: isFork,
|
||||||
|
isArchived: isArchived,
|
||||||
|
org: {
|
||||||
|
connect: {
|
||||||
|
id: orgId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
connections: {
|
||||||
|
create: {
|
||||||
|
connectionId: connectionId,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
gitConfig: {
|
||||||
|
'zoekt.web-url-type': codeHostType,
|
||||||
|
'zoekt.web-url': webUrl,
|
||||||
|
'zoekt.name': repoName,
|
||||||
|
'zoekt.archived': marshalBool(isArchived),
|
||||||
|
'zoekt.fork': marshalBool(isFork),
|
||||||
|
'zoekt.public': marshalBool(isPublic),
|
||||||
|
'zoekt.display-name': displayName,
|
||||||
|
},
|
||||||
|
branches: config.revisions?.branches ?? undefined,
|
||||||
|
tags: config.revisions?.tags ?? undefined,
|
||||||
|
} satisfies RepoMetadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
return record;
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
repoData: repos,
|
||||||
|
notFound,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ import { Job, Queue, Worker } from 'bullmq';
|
||||||
import { Redis } from 'ioredis';
|
import { Redis } from 'ioredis';
|
||||||
import { createLogger } from "./logger.js";
|
import { createLogger } from "./logger.js";
|
||||||
import { Connection, PrismaClient, Repo, RepoToConnection, RepoIndexingStatus, StripeSubscriptionStatus } from "@sourcebot/db";
|
import { Connection, PrismaClient, Repo, RepoToConnection, RepoIndexingStatus, StripeSubscriptionStatus } from "@sourcebot/db";
|
||||||
import { GithubConnectionConfig, GitlabConnectionConfig, GiteaConnectionConfig } from '@sourcebot/schemas/v3/connection.type';
|
import { GithubConnectionConfig, GitlabConnectionConfig, GiteaConnectionConfig, BitbucketConnectionConfig } from '@sourcebot/schemas/v3/connection.type';
|
||||||
import { AppContext, Settings, repoMetadataSchema } from "./types.js";
|
import { AppContext, Settings, repoMetadataSchema } from "./types.js";
|
||||||
import { getRepoPath, getTokenFromConfig, measure, getShardPrefix } from "./utils.js";
|
import { getRepoPath, getTokenFromConfig, measure, getShardPrefix } from "./utils.js";
|
||||||
import { cloneRepository, fetchRepository, upsertGitConfig } from "./git.js";
|
import { cloneRepository, fetchRepository, upsertGitConfig } from "./git.js";
|
||||||
|
|
@ -170,31 +170,50 @@ export class RepoManager implements IRepoManager {
|
||||||
// fetch the token here using the connections from the repo. Multiple connections could be referencing this repo, and each
|
// fetch the token here using the connections from the repo. Multiple connections could be referencing this repo, and each
|
||||||
// may have their own token. This method will just pick the first connection that has a token (if one exists) and uses that. This
|
// may have their own token. This method will just pick the first connection that has a token (if one exists) and uses that. This
|
||||||
// may technically cause syncing to fail if that connection's token just so happens to not have access to the repo it's referrencing.
|
// may technically cause syncing to fail if that connection's token just so happens to not have access to the repo it's referrencing.
|
||||||
private async getTokenForRepo(repo: RepoWithConnections, db: PrismaClient) {
|
private async getAuthForRepo(repo: RepoWithConnections, db: PrismaClient): Promise<{ username: string, password: string } | undefined> {
|
||||||
const repoConnections = repo.connections;
|
const repoConnections = repo.connections;
|
||||||
if (repoConnections.length === 0) {
|
if (repoConnections.length === 0) {
|
||||||
this.logger.error(`Repo ${repo.id} has no connections`);
|
this.logger.error(`Repo ${repo.id} has no connections`);
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let username = (() => {
|
||||||
|
switch (repo.external_codeHostType) {
|
||||||
|
case 'gitlab':
|
||||||
|
return 'oauth2';
|
||||||
|
case 'bitbucket-cloud':
|
||||||
|
case 'bitbucket-server':
|
||||||
|
case 'github':
|
||||||
|
case 'gitea':
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
let token: string | undefined;
|
let password: string | undefined = undefined;
|
||||||
for (const repoConnection of repoConnections) {
|
for (const repoConnection of repoConnections) {
|
||||||
const connection = repoConnection.connection;
|
const connection = repoConnection.connection;
|
||||||
if (connection.connectionType !== 'github' && connection.connectionType !== 'gitlab' && connection.connectionType !== 'gitea') {
|
if (connection.connectionType !== 'github' && connection.connectionType !== 'gitlab' && connection.connectionType !== 'gitea' && connection.connectionType !== 'bitbucket') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = connection.config as unknown as GithubConnectionConfig | GitlabConnectionConfig | GiteaConnectionConfig;
|
const config = connection.config as unknown as GithubConnectionConfig | GitlabConnectionConfig | GiteaConnectionConfig | BitbucketConnectionConfig;
|
||||||
if (config.token) {
|
if (config.token) {
|
||||||
token = await getTokenFromConfig(config.token, connection.orgId, db, this.logger);
|
password = await getTokenFromConfig(config.token, connection.orgId, db, this.logger);
|
||||||
if (token) {
|
if (password) {
|
||||||
|
// If we're using a bitbucket connection we need to set the username to be able to clone the repo
|
||||||
|
if (connection.connectionType === 'bitbucket') {
|
||||||
|
const bitbucketConfig = config as BitbucketConnectionConfig;
|
||||||
|
username = bitbucketConfig.user ?? "x-token-auth";
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return token;
|
return password
|
||||||
|
? { username, password }
|
||||||
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async syncGitRepository(repo: RepoWithConnections, repoAlreadyInIndexingState: boolean) {
|
private async syncGitRepository(repo: RepoWithConnections, repoAlreadyInIndexingState: boolean) {
|
||||||
|
|
@ -225,20 +244,11 @@ export class RepoManager implements IRepoManager {
|
||||||
} else {
|
} else {
|
||||||
this.logger.info(`Cloning ${repo.displayName}...`);
|
this.logger.info(`Cloning ${repo.displayName}...`);
|
||||||
|
|
||||||
const token = await this.getTokenForRepo(repo, this.db);
|
const auth = await this.getAuthForRepo(repo, this.db);
|
||||||
const cloneUrl = new URL(repo.cloneUrl);
|
const cloneUrl = new URL(repo.cloneUrl);
|
||||||
if (token) {
|
if (auth) {
|
||||||
switch (repo.external_codeHostType) {
|
cloneUrl.username = auth.username;
|
||||||
case 'gitlab':
|
cloneUrl.password = auth.password;
|
||||||
cloneUrl.username = 'oauth2';
|
|
||||||
cloneUrl.password = token;
|
|
||||||
break;
|
|
||||||
case 'gitea':
|
|
||||||
case 'github':
|
|
||||||
default:
|
|
||||||
cloneUrl.username = token;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { durationMs } = await measure(() => cloneRepository(cloneUrl.toString(), repoPath, ({ method, stage, progress }) => {
|
const { durationMs } = await measure(() => cloneRepository(cloneUrl.toString(), repoPath, ({ method, stage, progress }) => {
|
||||||
|
|
@ -318,12 +328,12 @@ export class RepoManager implements IRepoManager {
|
||||||
attempts++;
|
attempts++;
|
||||||
this.promClient.repoIndexingReattemptsTotal.inc();
|
this.promClient.repoIndexingReattemptsTotal.inc();
|
||||||
if (attempts === maxAttempts) {
|
if (attempts === maxAttempts) {
|
||||||
this.logger.error(`Failed to sync repository ${repo.id} after ${maxAttempts} attempts. Error: ${error}`);
|
this.logger.error(`Failed to sync repository ${repo.name} (id: ${repo.id}) after ${maxAttempts} attempts. Error: ${error}`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sleepDuration = 5000 * Math.pow(2, attempts - 1);
|
const sleepDuration = 5000 * Math.pow(2, attempts - 1);
|
||||||
this.logger.error(`Failed to sync repository ${repo.id}, attempt ${attempts}/${maxAttempts}. Sleeping for ${sleepDuration / 1000}s... Error: ${error}`);
|
this.logger.error(`Failed to sync repository ${repo.name} (id: ${repo.id}), attempt ${attempts}/${maxAttempts}. Sleeping for ${sleepDuration / 1000}s... Error: ${error}`);
|
||||||
await new Promise(resolve => setTimeout(resolve, sleepDuration));
|
await new Promise(resolve => setTimeout(resolve, sleepDuration));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -453,7 +463,7 @@ export class RepoManager implements IRepoManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async runGarbageCollectionJob(job: Job<RepoGarbageCollectionPayload>) {
|
private async runGarbageCollectionJob(job: Job<RepoGarbageCollectionPayload>) {
|
||||||
this.logger.info(`Running garbage collection job (id: ${job.id}) for repo ${job.data.repo.id}`);
|
this.logger.info(`Running garbage collection job (id: ${job.id}) for repo ${job.data.repo.displayName} (id: ${job.data.repo.id})`);
|
||||||
this.promClient.activeRepoGarbageCollectionJobs.inc();
|
this.promClient.activeRepoGarbageCollectionJobs.inc();
|
||||||
|
|
||||||
const repo = job.data.repo as Repo;
|
const repo = job.data.repo as Repo;
|
||||||
|
|
|
||||||
179
packages/schemas/src/v3/bitbucket.schema.ts
Normal file
179
packages/schemas/src/v3/bitbucket.schema.ts
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY!
|
||||||
|
const schema = {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"title": "BitbucketConnectionConfig",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "bitbucket",
|
||||||
|
"description": "Bitbucket configuration"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The username to use for authentication. Only needed if token is an app password."
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"description": "An authentication token.",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"secret": "SECRET_KEY"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"secret": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the secret that contains the token."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"secret"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"env": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the environment variable that contains the token. Only supported in declarative connection configs."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"env"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "url",
|
||||||
|
"default": "https://api.bitbucket.org/2.0",
|
||||||
|
"description": "Bitbucket URL",
|
||||||
|
"examples": [
|
||||||
|
"https://bitbucket.example.com"
|
||||||
|
],
|
||||||
|
"pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$"
|
||||||
|
},
|
||||||
|
"deploymentType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"cloud",
|
||||||
|
"server"
|
||||||
|
],
|
||||||
|
"default": "cloud",
|
||||||
|
"description": "The type of Bitbucket deployment"
|
||||||
|
},
|
||||||
|
"workspaces": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of workspaces to sync. Ignored if deploymentType is server."
|
||||||
|
},
|
||||||
|
"projects": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of projects to sync"
|
||||||
|
},
|
||||||
|
"repos": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of repos to sync"
|
||||||
|
},
|
||||||
|
"exclude": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"archived": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Exclude archived repositories from syncing."
|
||||||
|
},
|
||||||
|
"forks": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Exclude forked repositories from syncing."
|
||||||
|
},
|
||||||
|
"repos": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
[
|
||||||
|
"cloud_workspace/repo1",
|
||||||
|
"server_project/repo2"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"description": "List of specific repos to exclude from syncing."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"revisions": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "The revisions (branches, tags) that should be included when indexing. The default branch (HEAD) is always indexed. A maximum of 64 revisions can be indexed, with any additional revisions being ignored.",
|
||||||
|
"properties": {
|
||||||
|
"branches": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "List of branches to include when indexing. For a given repo, only the branches that exist on the repo's remote *and* match at least one of the provided `branches` will be indexed. The default branch (HEAD) is always indexed. Glob patterns are supported. A maximum of 64 branches can be indexed, with any additional branches being ignored.",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
[
|
||||||
|
"main",
|
||||||
|
"release/*"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"**"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "List of tags to include when indexing. For a given repo, only the tags that exist on the repo's remote *and* match at least one of the provided `tags` will be indexed. Glob patterns are supported. A maximum of 64 tags can be indexed, with any additional tags being ignored.",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
[
|
||||||
|
"latest",
|
||||||
|
"v2.*.*"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"**"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"default": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"deploymentType": {
|
||||||
|
"const": "server"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"required": [
|
||||||
|
"url"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
} as const;
|
||||||
|
export { schema as bitbucketSchema };
|
||||||
76
packages/schemas/src/v3/bitbucket.type.ts
Normal file
76
packages/schemas/src/v3/bitbucket.type.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY!
|
||||||
|
|
||||||
|
export interface BitbucketConnectionConfig {
|
||||||
|
/**
|
||||||
|
* Bitbucket configuration
|
||||||
|
*/
|
||||||
|
type: "bitbucket";
|
||||||
|
/**
|
||||||
|
* The username to use for authentication. Only needed if token is an app password.
|
||||||
|
*/
|
||||||
|
user?: string;
|
||||||
|
/**
|
||||||
|
* An authentication token.
|
||||||
|
*/
|
||||||
|
token?:
|
||||||
|
| {
|
||||||
|
/**
|
||||||
|
* The name of the secret that contains the token.
|
||||||
|
*/
|
||||||
|
secret: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/**
|
||||||
|
* The name of the environment variable that contains the token. Only supported in declarative connection configs.
|
||||||
|
*/
|
||||||
|
env: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Bitbucket URL
|
||||||
|
*/
|
||||||
|
url?: string;
|
||||||
|
/**
|
||||||
|
* The type of Bitbucket deployment
|
||||||
|
*/
|
||||||
|
deploymentType?: "cloud" | "server";
|
||||||
|
/**
|
||||||
|
* List of workspaces to sync. Ignored if deploymentType is server.
|
||||||
|
*/
|
||||||
|
workspaces?: string[];
|
||||||
|
/**
|
||||||
|
* List of projects to sync
|
||||||
|
*/
|
||||||
|
projects?: string[];
|
||||||
|
/**
|
||||||
|
* List of repos to sync
|
||||||
|
*/
|
||||||
|
repos?: string[];
|
||||||
|
exclude?: {
|
||||||
|
/**
|
||||||
|
* Exclude archived repositories from syncing.
|
||||||
|
*/
|
||||||
|
archived?: boolean;
|
||||||
|
/**
|
||||||
|
* Exclude forked repositories from syncing.
|
||||||
|
*/
|
||||||
|
forks?: boolean;
|
||||||
|
/**
|
||||||
|
* List of specific repos to exclude from syncing.
|
||||||
|
*/
|
||||||
|
repos?: string[];
|
||||||
|
};
|
||||||
|
revisions?: GitRevisions;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* The revisions (branches, tags) that should be included when indexing. The default branch (HEAD) is always indexed. A maximum of 64 revisions can be indexed, with any additional revisions being ignored.
|
||||||
|
*/
|
||||||
|
export interface GitRevisions {
|
||||||
|
/**
|
||||||
|
* List of branches to include when indexing. For a given repo, only the branches that exist on the repo's remote *and* match at least one of the provided `branches` will be indexed. The default branch (HEAD) is always indexed. Glob patterns are supported. A maximum of 64 branches can be indexed, with any additional branches being ignored.
|
||||||
|
*/
|
||||||
|
branches?: string[];
|
||||||
|
/**
|
||||||
|
* List of tags to include when indexing. For a given repo, only the tags that exist on the repo's remote *and* match at least one of the provided `tags` will be indexed. Glob patterns are supported. A maximum of 64 tags can be indexed, with any additional tags being ignored.
|
||||||
|
*/
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
|
|
@ -504,6 +504,118 @@ const schema = {
|
||||||
"url"
|
"url"
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"title": "BitbucketConnectionConfig",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "bitbucket",
|
||||||
|
"description": "Bitbucket configuration"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The username to use for authentication. Only needed if token is an app password."
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"$ref": "#/oneOf/0/properties/token",
|
||||||
|
"description": "An authentication token.",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"secret": "SECRET_KEY"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "url",
|
||||||
|
"default": "https://api.bitbucket.org/2.0",
|
||||||
|
"description": "Bitbucket URL",
|
||||||
|
"examples": [
|
||||||
|
"https://bitbucket.example.com"
|
||||||
|
],
|
||||||
|
"pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$"
|
||||||
|
},
|
||||||
|
"deploymentType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"cloud",
|
||||||
|
"server"
|
||||||
|
],
|
||||||
|
"default": "cloud",
|
||||||
|
"description": "The type of Bitbucket deployment"
|
||||||
|
},
|
||||||
|
"workspaces": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of workspaces to sync. Ignored if deploymentType is server."
|
||||||
|
},
|
||||||
|
"projects": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of projects to sync"
|
||||||
|
},
|
||||||
|
"repos": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of repos to sync"
|
||||||
|
},
|
||||||
|
"exclude": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"archived": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Exclude archived repositories from syncing."
|
||||||
|
},
|
||||||
|
"forks": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Exclude forked repositories from syncing."
|
||||||
|
},
|
||||||
|
"repos": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
[
|
||||||
|
"cloud_workspace/repo1",
|
||||||
|
"server_project/repo2"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"description": "List of specific repos to exclude from syncing."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"revisions": {
|
||||||
|
"$ref": "#/oneOf/0/properties/revisions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"deploymentType": {
|
||||||
|
"const": "server"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"required": [
|
||||||
|
"url"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ export type ConnectionConfig =
|
||||||
| GithubConnectionConfig
|
| GithubConnectionConfig
|
||||||
| GitlabConnectionConfig
|
| GitlabConnectionConfig
|
||||||
| GiteaConnectionConfig
|
| GiteaConnectionConfig
|
||||||
| GerritConnectionConfig;
|
| GerritConnectionConfig
|
||||||
|
| BitbucketConnectionConfig;
|
||||||
|
|
||||||
export interface GithubConnectionConfig {
|
export interface GithubConnectionConfig {
|
||||||
/**
|
/**
|
||||||
|
|
@ -235,3 +236,64 @@ export interface GerritConnectionConfig {
|
||||||
projects?: string[];
|
projects?: string[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
export interface BitbucketConnectionConfig {
|
||||||
|
/**
|
||||||
|
* Bitbucket configuration
|
||||||
|
*/
|
||||||
|
type: "bitbucket";
|
||||||
|
/**
|
||||||
|
* The username to use for authentication. Only needed if token is an app password.
|
||||||
|
*/
|
||||||
|
user?: string;
|
||||||
|
/**
|
||||||
|
* An authentication token.
|
||||||
|
*/
|
||||||
|
token?:
|
||||||
|
| {
|
||||||
|
/**
|
||||||
|
* The name of the secret that contains the token.
|
||||||
|
*/
|
||||||
|
secret: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/**
|
||||||
|
* The name of the environment variable that contains the token. Only supported in declarative connection configs.
|
||||||
|
*/
|
||||||
|
env: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Bitbucket URL
|
||||||
|
*/
|
||||||
|
url?: string;
|
||||||
|
/**
|
||||||
|
* The type of Bitbucket deployment
|
||||||
|
*/
|
||||||
|
deploymentType?: "cloud" | "server";
|
||||||
|
/**
|
||||||
|
* List of workspaces to sync. Ignored if deploymentType is server.
|
||||||
|
*/
|
||||||
|
workspaces?: string[];
|
||||||
|
/**
|
||||||
|
* List of projects to sync
|
||||||
|
*/
|
||||||
|
projects?: string[];
|
||||||
|
/**
|
||||||
|
* List of repos to sync
|
||||||
|
*/
|
||||||
|
repos?: string[];
|
||||||
|
exclude?: {
|
||||||
|
/**
|
||||||
|
* Exclude archived repositories from syncing.
|
||||||
|
*/
|
||||||
|
archived?: boolean;
|
||||||
|
/**
|
||||||
|
* Exclude forked repositories from syncing.
|
||||||
|
*/
|
||||||
|
forks?: boolean;
|
||||||
|
/**
|
||||||
|
* List of specific repos to exclude from syncing.
|
||||||
|
*/
|
||||||
|
repos?: string[];
|
||||||
|
};
|
||||||
|
revisions?: GitRevisions;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -633,6 +633,118 @@ const schema = {
|
||||||
"url"
|
"url"
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"title": "BitbucketConnectionConfig",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "bitbucket",
|
||||||
|
"description": "Bitbucket configuration"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The username to use for authentication. Only needed if token is an app password."
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"$ref": "#/properties/connections/patternProperties/%5E%5Ba-zA-Z0-9_-%5D%2B%24/oneOf/0/properties/token",
|
||||||
|
"description": "An authentication token.",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"secret": "SECRET_KEY"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "url",
|
||||||
|
"default": "https://api.bitbucket.org/2.0",
|
||||||
|
"description": "Bitbucket URL",
|
||||||
|
"examples": [
|
||||||
|
"https://bitbucket.example.com"
|
||||||
|
],
|
||||||
|
"pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$"
|
||||||
|
},
|
||||||
|
"deploymentType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"cloud",
|
||||||
|
"server"
|
||||||
|
],
|
||||||
|
"default": "cloud",
|
||||||
|
"description": "The type of Bitbucket deployment"
|
||||||
|
},
|
||||||
|
"workspaces": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of workspaces to sync. Ignored if deploymentType is server."
|
||||||
|
},
|
||||||
|
"projects": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of projects to sync"
|
||||||
|
},
|
||||||
|
"repos": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of repos to sync"
|
||||||
|
},
|
||||||
|
"exclude": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"archived": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Exclude archived repositories from syncing."
|
||||||
|
},
|
||||||
|
"forks": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Exclude forked repositories from syncing."
|
||||||
|
},
|
||||||
|
"repos": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
[
|
||||||
|
"cloud_workspace/repo1",
|
||||||
|
"server_project/repo2"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"description": "List of specific repos to exclude from syncing."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"revisions": {
|
||||||
|
"$ref": "#/properties/connections/patternProperties/%5E%5Ba-zA-Z0-9_-%5D%2B%24/oneOf/0/properties/revisions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"deploymentType": {
|
||||||
|
"const": "server"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"required": [
|
||||||
|
"url"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ export type ConnectionConfig =
|
||||||
| GithubConnectionConfig
|
| GithubConnectionConfig
|
||||||
| GitlabConnectionConfig
|
| GitlabConnectionConfig
|
||||||
| GiteaConnectionConfig
|
| GiteaConnectionConfig
|
||||||
| GerritConnectionConfig;
|
| GerritConnectionConfig
|
||||||
|
| BitbucketConnectionConfig;
|
||||||
|
|
||||||
export interface SourcebotConfig {
|
export interface SourcebotConfig {
|
||||||
$schema?: string;
|
$schema?: string;
|
||||||
|
|
@ -330,3 +331,64 @@ export interface GerritConnectionConfig {
|
||||||
projects?: string[];
|
projects?: string[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
export interface BitbucketConnectionConfig {
|
||||||
|
/**
|
||||||
|
* Bitbucket configuration
|
||||||
|
*/
|
||||||
|
type: "bitbucket";
|
||||||
|
/**
|
||||||
|
* The username to use for authentication. Only needed if token is an app password.
|
||||||
|
*/
|
||||||
|
user?: string;
|
||||||
|
/**
|
||||||
|
* An authentication token.
|
||||||
|
*/
|
||||||
|
token?:
|
||||||
|
| {
|
||||||
|
/**
|
||||||
|
* The name of the secret that contains the token.
|
||||||
|
*/
|
||||||
|
secret: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/**
|
||||||
|
* The name of the environment variable that contains the token. Only supported in declarative connection configs.
|
||||||
|
*/
|
||||||
|
env: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Bitbucket URL
|
||||||
|
*/
|
||||||
|
url?: string;
|
||||||
|
/**
|
||||||
|
* The type of Bitbucket deployment
|
||||||
|
*/
|
||||||
|
deploymentType?: "cloud" | "server";
|
||||||
|
/**
|
||||||
|
* List of workspaces to sync. Ignored if deploymentType is server.
|
||||||
|
*/
|
||||||
|
workspaces?: string[];
|
||||||
|
/**
|
||||||
|
* List of projects to sync
|
||||||
|
*/
|
||||||
|
projects?: string[];
|
||||||
|
/**
|
||||||
|
* List of repos to sync
|
||||||
|
*/
|
||||||
|
repos?: string[];
|
||||||
|
exclude?: {
|
||||||
|
/**
|
||||||
|
* Exclude archived repositories from syncing.
|
||||||
|
*/
|
||||||
|
archived?: boolean;
|
||||||
|
/**
|
||||||
|
* Exclude forked repositories from syncing.
|
||||||
|
*/
|
||||||
|
forks?: boolean;
|
||||||
|
/**
|
||||||
|
* List of specific repos to exclude from syncing.
|
||||||
|
*/
|
||||||
|
repos?: string[];
|
||||||
|
};
|
||||||
|
revisions?: GitRevisions;
|
||||||
|
}
|
||||||
|
|
|
||||||
5
packages/web/public/bitbucket.svg
Normal file
5
packages/web/public/bitbucket.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-label="Bitbucket" role="img"
|
||||||
|
viewBox="0 0 512 512">
|
||||||
|
<path fill="#2684ff" d="M422 130a10 10 0 00-9.9-11.7H100.5a10 10 0 00-10 11.7L136 409a10 10 0 009.9 8.4h221c5 0 9.2-3.5 10 -8.4L422 130zM291 316.8h-69.3l-18.7-98h104.8z"/><path fill="url(#a)" d="M59.632 25.2H40.94l-3.1 18.3h-13v18.9H52c1 0 1.7-.7 1.8-1.6l5.8-35.6z" transform="translate(89.8 85) scale(5.3285)"/><linearGradient id="a" x2="1" gradientTransform="rotate(141 22.239 22.239) scale(31.4)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0052cc"/><stop offset="1" stop-color="#2684ff"/></linearGradient></svg>
|
||||||
|
After Width: | Height: | Size: 748 B |
|
|
@ -3,7 +3,7 @@
|
||||||
import { env } from "@/env.mjs";
|
import { env } from "@/env.mjs";
|
||||||
import { ErrorCode } from "@/lib/errorCodes";
|
import { ErrorCode } from "@/lib/errorCodes";
|
||||||
import { notAuthenticated, notFound, secretAlreadyExists, ServiceError, unexpectedError } from "@/lib/serviceError";
|
import { notAuthenticated, notFound, secretAlreadyExists, ServiceError, unexpectedError } from "@/lib/serviceError";
|
||||||
import { isServiceError } from "@/lib/utils";
|
import { CodeHostType, isServiceError } from "@/lib/utils";
|
||||||
import { prisma } from "@/prisma";
|
import { prisma } from "@/prisma";
|
||||||
import { render } from "@react-email/components";
|
import { render } from "@react-email/components";
|
||||||
import * as Sentry from '@sentry/nextjs';
|
import * as Sentry from '@sentry/nextjs';
|
||||||
|
|
@ -27,6 +27,7 @@ import { MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME, SINGLE_TENANT_U
|
||||||
import { orgDomainSchema, orgNameSchema, repositoryQuerySchema } from "./lib/schemas";
|
import { orgDomainSchema, orgNameSchema, repositoryQuerySchema } from "./lib/schemas";
|
||||||
import { TenancyMode } from "./lib/types";
|
import { TenancyMode } from "./lib/types";
|
||||||
import { decrementOrgSeatCount, getSubscriptionForOrg, incrementOrgSeatCount } from "./ee/features/billing/serverUtils";
|
import { decrementOrgSeatCount, getSubscriptionForOrg, incrementOrgSeatCount } from "./ee/features/billing/serverUtils";
|
||||||
|
import { bitbucketSchema } from "@sourcebot/schemas/v3/bitbucket.schema";
|
||||||
|
|
||||||
const ajv = new Ajv({
|
const ajv = new Ajv({
|
||||||
validateFormats: false,
|
validateFormats: false,
|
||||||
|
|
@ -442,10 +443,10 @@ export const getRepos = async (domain: string, filter: { status?: RepoIndexingSt
|
||||||
}
|
}
|
||||||
), /* allowSingleTenantUnauthedAccess = */ true));
|
), /* allowSingleTenantUnauthedAccess = */ true));
|
||||||
|
|
||||||
export const createConnection = async (name: string, type: string, connectionConfig: string, domain: string): Promise<{ id: number } | ServiceError> => sew(() =>
|
export const createConnection = async (name: string, type: CodeHostType, connectionConfig: string, domain: string): Promise<{ id: number } | ServiceError> => sew(() =>
|
||||||
withAuth((session) =>
|
withAuth((session) =>
|
||||||
withOrgMembership(session, domain, async ({ orgId }) => {
|
withOrgMembership(session, domain, async ({ orgId }) => {
|
||||||
const parsedConfig = parseConnectionConfig(type, connectionConfig);
|
const parsedConfig = parseConnectionConfig(connectionConfig);
|
||||||
if (isServiceError(parsedConfig)) {
|
if (isServiceError(parsedConfig)) {
|
||||||
return parsedConfig;
|
return parsedConfig;
|
||||||
}
|
}
|
||||||
|
|
@ -531,7 +532,7 @@ export const updateConnectionConfigAndScheduleSync = async (connectionId: number
|
||||||
return notFound();
|
return notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedConfig = parseConnectionConfig(connection.connectionType, config);
|
const parsedConfig = parseConnectionConfig(config);
|
||||||
if (isServiceError(parsedConfig)) {
|
if (isServiceError(parsedConfig)) {
|
||||||
return parsedConfig;
|
return parsedConfig;
|
||||||
}
|
}
|
||||||
|
|
@ -1154,7 +1155,7 @@ export const getSearchContexts = async (domain: string) => sew(() =>
|
||||||
|
|
||||||
////// Helpers ///////
|
////// Helpers ///////
|
||||||
|
|
||||||
const parseConnectionConfig = (connectionType: string, config: string) => {
|
const parseConnectionConfig = (config: string) => {
|
||||||
let parsedConfig: ConnectionConfig;
|
let parsedConfig: ConnectionConfig;
|
||||||
try {
|
try {
|
||||||
parsedConfig = JSON.parse(config);
|
parsedConfig = JSON.parse(config);
|
||||||
|
|
@ -1166,6 +1167,7 @@ const parseConnectionConfig = (connectionType: string, config: string) => {
|
||||||
} satisfies ServiceError;
|
} satisfies ServiceError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const connectionType = parsedConfig.type;
|
||||||
const schema = (() => {
|
const schema = (() => {
|
||||||
switch (connectionType) {
|
switch (connectionType) {
|
||||||
case "github":
|
case "github":
|
||||||
|
|
@ -1176,6 +1178,8 @@ const parseConnectionConfig = (connectionType: string, config: string) => {
|
||||||
return giteaSchema;
|
return giteaSchema;
|
||||||
case 'gerrit':
|
case 'gerrit':
|
||||||
return gerritSchema;
|
return gerritSchema;
|
||||||
|
case 'bitbucket':
|
||||||
|
return bitbucketSchema;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
@ -1205,9 +1209,10 @@ const parseConnectionConfig = (connectionType: string, config: string) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { numRepos, hasToken } = (() => {
|
const { numRepos, hasToken } = (() => {
|
||||||
switch (parsedConfig.type) {
|
switch (connectionType) {
|
||||||
case "gitea":
|
case "gitea":
|
||||||
case "github": {
|
case "github":
|
||||||
|
case "bitbucket": {
|
||||||
return {
|
return {
|
||||||
numRepos: parsedConfig.repos?.length,
|
numRepos: parsedConfig.repos?.length,
|
||||||
hasToken: !!parsedConfig.token,
|
hasToken: !!parsedConfig.token,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ export const CodeHostIconButton = ({
|
||||||
const captureEvent = useCaptureEvent();
|
const captureEvent = useCaptureEvent();
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className="flex flex-col items-center justify-center p-4 w-24 h-24 cursor-pointer gap-2"
|
className="flex flex-col items-center justify-center p-4 w-36 h-36 cursor-pointer gap-2"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
captureEvent('wa_connect_code_host_button_pressed', {
|
captureEvent('wa_connect_code_host_button_pressed', {
|
||||||
|
|
@ -29,7 +29,7 @@ export const CodeHostIconButton = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image src={logo.src} alt={name} className={cn("w-8 h-8", logo.className)} />
|
<Image src={logo.src} alt={name} className={cn("w-8 h-8", logo.className)} />
|
||||||
<p className="text-sm font-medium">{name}</p>
|
<p className="text-sm font-medium text-center">{name}</p>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import SharedConnectionCreationForm from "./sharedConnectionCreationForm";
|
||||||
|
import { BitbucketConnectionConfig } from "@sourcebot/schemas/v3/connection.type";
|
||||||
|
import { bitbucketSchema } from "@sourcebot/schemas/v3/bitbucket.schema";
|
||||||
|
import { bitbucketCloudQuickActions } from "../../connections/quickActions";
|
||||||
|
|
||||||
|
interface BitbucketCloudConnectionCreationFormProps {
|
||||||
|
onCreated?: (id: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const additionalConfigValidation = (config: BitbucketConnectionConfig): { message: string, isValid: boolean } => {
|
||||||
|
const hasProjects = config.projects && config.projects.length > 0 && config.projects.some(p => p.trim().length > 0);
|
||||||
|
const hasRepos = config.repos && config.repos.length > 0 && config.repos.some(r => r.trim().length > 0);
|
||||||
|
const hasWorkspaces = config.workspaces && config.workspaces.length > 0 && config.workspaces.some(w => w.trim().length > 0);
|
||||||
|
|
||||||
|
if (!hasProjects && !hasRepos && !hasWorkspaces) {
|
||||||
|
return {
|
||||||
|
message: "At least one project, repository, or workspace must be specified",
|
||||||
|
isValid: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: "Valid",
|
||||||
|
isValid: true,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BitbucketCloudConnectionCreationForm = ({ onCreated }: BitbucketCloudConnectionCreationFormProps) => {
|
||||||
|
const defaultConfig: BitbucketConnectionConfig = {
|
||||||
|
type: 'bitbucket',
|
||||||
|
deploymentType: 'cloud',
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SharedConnectionCreationForm<BitbucketConnectionConfig>
|
||||||
|
type="bitbucket-cloud"
|
||||||
|
title="Create a Bitbucket Cloud connection"
|
||||||
|
defaultValues={{
|
||||||
|
config: JSON.stringify(defaultConfig, null, 2),
|
||||||
|
}}
|
||||||
|
schema={bitbucketSchema}
|
||||||
|
additionalConfigValidation={additionalConfigValidation}
|
||||||
|
quickActions={bitbucketCloudQuickActions}
|
||||||
|
onCreated={onCreated}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import SharedConnectionCreationForm from "./sharedConnectionCreationForm";
|
||||||
|
import { BitbucketConnectionConfig } from "@sourcebot/schemas/v3/connection.type";
|
||||||
|
import { bitbucketSchema } from "@sourcebot/schemas/v3/bitbucket.schema";
|
||||||
|
import { bitbucketDataCenterQuickActions } from "../../connections/quickActions";
|
||||||
|
|
||||||
|
interface BitbucketDataCenterConnectionCreationFormProps {
|
||||||
|
onCreated?: (id: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const additionalConfigValidation = (config: BitbucketConnectionConfig): { message: string, isValid: boolean } => {
|
||||||
|
const hasProjects = config.projects && config.projects.length > 0 && config.projects.some(p => p.trim().length > 0);
|
||||||
|
const hasRepos = config.repos && config.repos.length > 0 && config.repos.some(r => r.trim().length > 0);
|
||||||
|
|
||||||
|
if (!hasProjects && !hasRepos) {
|
||||||
|
return {
|
||||||
|
message: "At least one project or repository must be specified",
|
||||||
|
isValid: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: "Valid",
|
||||||
|
isValid: true,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BitbucketDataCenterConnectionCreationForm = ({ onCreated }: BitbucketDataCenterConnectionCreationFormProps) => {
|
||||||
|
const defaultConfig: BitbucketConnectionConfig = {
|
||||||
|
type: 'bitbucket',
|
||||||
|
deploymentType: 'server',
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SharedConnectionCreationForm<BitbucketConnectionConfig>
|
||||||
|
type="bitbucket-server"
|
||||||
|
title="Create a Bitbucket Data Center connection"
|
||||||
|
defaultValues={{
|
||||||
|
config: JSON.stringify(defaultConfig, null, 2),
|
||||||
|
}}
|
||||||
|
schema={bitbucketSchema}
|
||||||
|
additionalConfigValidation={additionalConfigValidation}
|
||||||
|
quickActions={bitbucketDataCenterQuickActions}
|
||||||
|
onCreated={onCreated}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -2,3 +2,5 @@ export { GitHubConnectionCreationForm } from "./githubConnectionCreationForm";
|
||||||
export { GitLabConnectionCreationForm } from "./gitlabConnectionCreationForm";
|
export { GitLabConnectionCreationForm } from "./gitlabConnectionCreationForm";
|
||||||
export { GiteaConnectionCreationForm } from "./giteaConnectionCreationForm";
|
export { GiteaConnectionCreationForm } from "./giteaConnectionCreationForm";
|
||||||
export { GerritConnectionCreationForm } from "./gerritConnectionCreationForm";
|
export { GerritConnectionCreationForm } from "./gerritConnectionCreationForm";
|
||||||
|
export { BitbucketCloudConnectionCreationForm } from "./bitbucketCloudConnectionCreationForm";
|
||||||
|
export { BitbucketDataCenterConnectionCreationForm } from "./bitbucketDataCenterConnectionCreationForm";
|
||||||
|
|
@ -88,6 +88,10 @@ export const ImportSecretDialog = ({ open, onOpenChange, onSecretCreated, codeHo
|
||||||
return <GitHubPATCreationStep step={1} />;
|
return <GitHubPATCreationStep step={1} />;
|
||||||
case 'gitlab':
|
case 'gitlab':
|
||||||
return <GitLabPATCreationStep step={1} />;
|
return <GitLabPATCreationStep step={1} />;
|
||||||
|
case 'bitbucket-cloud':
|
||||||
|
return <BitbucketCloudPATCreationStep step={1} />;
|
||||||
|
case 'bitbucket-server':
|
||||||
|
return <BitbucketServerPATCreationStep step={1} />;
|
||||||
case 'gitea':
|
case 'gitea':
|
||||||
return <GiteaPATCreationStep step={1} />;
|
return <GiteaPATCreationStep step={1} />;
|
||||||
case 'gerrit':
|
case 'gerrit':
|
||||||
|
|
@ -179,7 +183,7 @@ export const ImportSecretDialog = ({ open, onOpenChange, onSecretCreated, codeHo
|
||||||
<FormLabel>Key</FormLabel>
|
<FormLabel>Key</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder="my-github-token"
|
placeholder="my-access-token"
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
@ -262,11 +266,33 @@ const GiteaPATCreationStep = ({ step }: { step: number }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BitbucketCloudPATCreationStep = ({ step }: { step: number }) => {
|
||||||
|
return (
|
||||||
|
<SecretCreationStep
|
||||||
|
step={step}
|
||||||
|
title="Create an Access Token"
|
||||||
|
description=<span>Please check out our <Link href="https://docs.sourcebot.dev/docs/connections/bitbucket-cloud#authenticating-with-bitbucket-cloud" target="_blank" className="underline">docs</Link> for more information on how to create auth credentials for Bitbucket Cloud.</span>
|
||||||
|
>
|
||||||
|
</SecretCreationStep>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const BitbucketServerPATCreationStep = ({ step }: { step: number }) => {
|
||||||
|
return (
|
||||||
|
<SecretCreationStep
|
||||||
|
step={step}
|
||||||
|
title="Create an Access Token"
|
||||||
|
description=<span>Please check out our <Link href="https://docs.sourcebot.dev/docs/connections/bitbucket-data-center#authenticating-with-bitbucket-data-center" target="_blank" className="underline">docs</Link> for more information on how to create auth credentials for Bitbucket Data Center.</span>
|
||||||
|
>
|
||||||
|
</SecretCreationStep>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
interface SecretCreationStepProps {
|
interface SecretCreationStepProps {
|
||||||
step: number;
|
step: number;
|
||||||
title: string;
|
title: string;
|
||||||
description: string | React.ReactNode;
|
description: string | React.ReactNode;
|
||||||
children: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SecretCreationStep = ({ step, title, description, children }: SecretCreationStepProps) => {
|
const SecretCreationStep = ({ step, title, description, children }: SecretCreationStepProps) => {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import { createZodConnectionConfigValidator } from "../../utils";
|
||||||
import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type";
|
import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type";
|
||||||
import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/gitea.type";
|
import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/gitea.type";
|
||||||
import { GerritConnectionConfig } from "@sourcebot/schemas/v3/gerrit.type";
|
import { GerritConnectionConfig } from "@sourcebot/schemas/v3/gerrit.type";
|
||||||
import { githubQuickActions, gitlabQuickActions, giteaQuickActions, gerritQuickActions } from "../../quickActions";
|
import { githubQuickActions, gitlabQuickActions, giteaQuickActions, gerritQuickActions, bitbucketCloudQuickActions, bitbucketDataCenterQuickActions } from "../../quickActions";
|
||||||
import { Schema } from "ajv";
|
import { Schema } from "ajv";
|
||||||
import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
|
import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
|
||||||
import { gitlabSchema } from "@sourcebot/schemas/v3/gitlab.schema";
|
import { gitlabSchema } from "@sourcebot/schemas/v3/gitlab.schema";
|
||||||
|
|
@ -27,11 +27,13 @@ import { useDomain } from "@/hooks/useDomain";
|
||||||
import { SecretCombobox } from "@/app/[domain]/components/connectionCreationForms/secretCombobox";
|
import { SecretCombobox } from "@/app/[domain]/components/connectionCreationForms/secretCombobox";
|
||||||
import { ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
import { ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
||||||
import strings from "@/lib/strings";
|
import strings from "@/lib/strings";
|
||||||
|
import { bitbucketSchema } from "@sourcebot/schemas/v3/bitbucket.schema";
|
||||||
|
import { BitbucketConnectionConfig } from "@sourcebot/schemas/v3/bitbucket.type";
|
||||||
|
|
||||||
interface ConfigSettingProps {
|
interface ConfigSettingProps {
|
||||||
connectionId: number;
|
connectionId: number;
|
||||||
config: string;
|
config: string;
|
||||||
type: string;
|
type: CodeHostType;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,6 +58,24 @@ export const ConfigSetting = (props: ConfigSettingProps) => {
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === 'bitbucket-cloud') {
|
||||||
|
return <ConfigSettingInternal<BitbucketConnectionConfig>
|
||||||
|
{...props}
|
||||||
|
type="bitbucket-cloud"
|
||||||
|
quickActions={bitbucketCloudQuickActions}
|
||||||
|
schema={bitbucketSchema}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'bitbucket-server') {
|
||||||
|
return <ConfigSettingInternal<BitbucketConnectionConfig>
|
||||||
|
{...props}
|
||||||
|
type="bitbucket-server"
|
||||||
|
quickActions={bitbucketDataCenterQuickActions}
|
||||||
|
schema={bitbucketSchema}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
if (type === 'gitea') {
|
if (type === 'gitea') {
|
||||||
return <ConfigSettingInternal<GiteaConnectionConfig>
|
return <ConfigSettingInternal<GiteaConnectionConfig>
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,9 @@ import { getOrgMembership } from "@/actions"
|
||||||
import { isServiceError } from "@/lib/utils"
|
import { isServiceError } from "@/lib/utils"
|
||||||
import { notFound } from "next/navigation"
|
import { notFound } from "next/navigation"
|
||||||
import { OrgRole } from "@sourcebot/db"
|
import { OrgRole } from "@sourcebot/db"
|
||||||
|
import { CodeHostType } from "@/lib/utils"
|
||||||
|
import { BitbucketConnectionConfig } from "@sourcebot/schemas/v3/bitbucket.type"
|
||||||
|
|
||||||
interface ConnectionManagementPageProps {
|
interface ConnectionManagementPageProps {
|
||||||
params: {
|
params: {
|
||||||
domain: string
|
domain: string
|
||||||
|
|
@ -93,7 +96,7 @@ export default async function ConnectionManagementPage({ params, searchParams }:
|
||||||
<DisplayNameSetting connectionId={connection.id} name={connection.name} disabled={!isOwner} />
|
<DisplayNameSetting connectionId={connection.id} name={connection.name} disabled={!isOwner} />
|
||||||
<ConfigSetting
|
<ConfigSetting
|
||||||
connectionId={connection.id}
|
connectionId={connection.id}
|
||||||
type={connection.connectionType}
|
type={connection.connectionType as CodeHostType}
|
||||||
config={JSON.stringify(connection.config, null, 2)}
|
config={JSON.stringify(connection.config, null, 2)}
|
||||||
disabled={!isOwner}
|
disabled={!isOwner}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,18 @@ export const NewConnectionCard = ({ className, role }: NewConnectionCardProps) =
|
||||||
subtitle="Cloud and Self-Hosted supported."
|
subtitle="Cloud and Self-Hosted supported."
|
||||||
disabled={!isOwner}
|
disabled={!isOwner}
|
||||||
/>
|
/>
|
||||||
|
<Card
|
||||||
|
type="bitbucket-cloud"
|
||||||
|
title="Bitbucket Cloud"
|
||||||
|
subtitle="Fetch repos from Bitbucket Cloud."
|
||||||
|
disabled={!isOwner}
|
||||||
|
/>
|
||||||
|
<Card
|
||||||
|
type="bitbucket-server"
|
||||||
|
title="Bitbucket Data Center"
|
||||||
|
subtitle="Fetch repos from a Bitbucket DC instance."
|
||||||
|
disabled={!isOwner}
|
||||||
|
/>
|
||||||
<Card
|
<Card
|
||||||
type="gitea"
|
type="gitea"
|
||||||
title="Gitea"
|
title="Gitea"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ import {
|
||||||
GitHubConnectionCreationForm,
|
GitHubConnectionCreationForm,
|
||||||
GitLabConnectionCreationForm,
|
GitLabConnectionCreationForm,
|
||||||
GiteaConnectionCreationForm,
|
GiteaConnectionCreationForm,
|
||||||
GerritConnectionCreationForm
|
GerritConnectionCreationForm,
|
||||||
|
BitbucketCloudConnectionCreationForm,
|
||||||
|
BitbucketDataCenterConnectionCreationForm
|
||||||
} from "@/app/[domain]/components/connectionCreationForms";
|
} from "@/app/[domain]/components/connectionCreationForms";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { useDomain } from "@/hooks/useDomain";
|
import { useDomain } from "@/hooks/useDomain";
|
||||||
|
|
@ -37,5 +39,14 @@ export default function NewConnectionPage({
|
||||||
return <GerritConnectionCreationForm onCreated={onCreated} />;
|
return <GerritConnectionCreationForm onCreated={onCreated} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === 'bitbucket-cloud') {
|
||||||
|
return <BitbucketCloudConnectionCreationForm onCreated={onCreated} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'bitbucket-server') {
|
||||||
|
return <BitbucketDataCenterConnectionCreationForm onCreated={onCreated} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
router.push(`/${domain}/connections`);
|
router.push(`/${domain}/connections`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type"
|
import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type"
|
||||||
import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
|
import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
|
||||||
|
import { BitbucketConnectionConfig } from "@sourcebot/schemas/v3/bitbucket.type";
|
||||||
import { QuickAction } from "../components/configEditor";
|
import { QuickAction } from "../components/configEditor";
|
||||||
import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/connection.type";
|
import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/gitea.type";
|
||||||
import { GerritConnectionConfig } from "@sourcebot/schemas/v3/gerrit.type";
|
import { GerritConnectionConfig } from "@sourcebot/schemas/v3/gerrit.type";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
|
@ -100,7 +101,7 @@ export const githubQuickActions: QuickAction<GithubConnectionConfig>[] = [
|
||||||
...previous,
|
...previous,
|
||||||
url: previous.url ?? "https://github.example.com",
|
url: previous.url ?? "https://github.example.com",
|
||||||
}),
|
}),
|
||||||
name: "Set a custom url",
|
name: "Set url to GitHub instance",
|
||||||
selectionText: "https://github.example.com",
|
selectionText: "https://github.example.com",
|
||||||
description: <span>Set a custom GitHub host. Defaults to <Code>https://github.com</Code>.</span>
|
description: <span>Set a custom GitHub host. Defaults to <Code>https://github.com</Code>.</span>
|
||||||
},
|
},
|
||||||
|
|
@ -290,7 +291,7 @@ export const gitlabQuickActions: QuickAction<GitlabConnectionConfig>[] = [
|
||||||
...previous,
|
...previous,
|
||||||
url: previous.url ?? "https://gitlab.example.com",
|
url: previous.url ?? "https://gitlab.example.com",
|
||||||
}),
|
}),
|
||||||
name: "Set a custom url",
|
name: "Set url to GitLab instance",
|
||||||
selectionText: "https://gitlab.example.com",
|
selectionText: "https://gitlab.example.com",
|
||||||
description: <span>Set a custom GitLab host. Defaults to <Code>https://gitlab.com</Code>.</span>
|
description: <span>Set a custom GitLab host. Defaults to <Code>https://gitlab.com</Code>.</span>
|
||||||
},
|
},
|
||||||
|
|
@ -360,7 +361,7 @@ export const giteaQuickActions: QuickAction<GiteaConnectionConfig>[] = [
|
||||||
...previous,
|
...previous,
|
||||||
url: previous.url ?? "https://gitea.example.com",
|
url: previous.url ?? "https://gitea.example.com",
|
||||||
}),
|
}),
|
||||||
name: "Set a custom url",
|
name: "Set url to Gitea instance",
|
||||||
selectionText: "https://gitea.example.com",
|
selectionText: "https://gitea.example.com",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -390,3 +391,196 @@ export const gerritQuickActions: QuickAction<GerritConnectionConfig>[] = [
|
||||||
name: "Exclude a project",
|
name: "Exclude a project",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const bitbucketCloudQuickActions: QuickAction<BitbucketConnectionConfig>[] = [
|
||||||
|
{
|
||||||
|
// add user
|
||||||
|
fn: (previous: BitbucketConnectionConfig) => ({
|
||||||
|
...previous,
|
||||||
|
user: previous.user ?? "username"
|
||||||
|
}),
|
||||||
|
name: "Add username",
|
||||||
|
selectionText: "username",
|
||||||
|
description: (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span>Username to use for authentication. This is only required if you're using an App Password (stored in <Code>token</Code>) for authentication.</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fn: (previous: BitbucketConnectionConfig) => ({
|
||||||
|
...previous,
|
||||||
|
workspaces: [
|
||||||
|
...(previous.workspaces ?? []),
|
||||||
|
"myWorkspace"
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
name: "Add a workspace",
|
||||||
|
selectionText: "myWorkspace",
|
||||||
|
description: (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span>Add a workspace to sync with. Ensure the workspace is visible to the provided <Code>token</Code> (if any).</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fn: (previous: BitbucketConnectionConfig) => ({
|
||||||
|
...previous,
|
||||||
|
repos: [
|
||||||
|
...(previous.repos ?? []),
|
||||||
|
"myWorkspace/myRepo"
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
name: "Add a repo",
|
||||||
|
selectionText: "myWorkspace/myRepo",
|
||||||
|
description: (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span>Add an individual repository to sync with. Ensure the repository is visible to the provided <Code>token</Code> (if any).</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fn: (previous: BitbucketConnectionConfig) => ({
|
||||||
|
...previous,
|
||||||
|
projects: [
|
||||||
|
...(previous.projects ?? []),
|
||||||
|
"myProject"
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
name: "Add a project",
|
||||||
|
selectionText: "myProject",
|
||||||
|
description: (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span>Add a project to sync with. Ensure the project is visible to the provided <Code>token</Code> (if any).</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fn: (previous: BitbucketConnectionConfig) => ({
|
||||||
|
...previous,
|
||||||
|
exclude: {
|
||||||
|
...previous.exclude,
|
||||||
|
repos: [...(previous.exclude?.repos ?? []), "myWorkspace/myExcludedRepo"]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
name: "Exclude a repo",
|
||||||
|
selectionText: "myWorkspace/myExcludedRepo",
|
||||||
|
description: (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span>Exclude a repository from syncing. Glob patterns are supported.</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
// exclude forked
|
||||||
|
{
|
||||||
|
fn: (previous: BitbucketConnectionConfig) => ({
|
||||||
|
...previous,
|
||||||
|
exclude: {
|
||||||
|
...previous.exclude,
|
||||||
|
forks: true
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
name: "Exclude forked repos",
|
||||||
|
description: <span>Exclude forked repositories from syncing.</span>
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const bitbucketDataCenterQuickActions: QuickAction<BitbucketConnectionConfig>[] = [
|
||||||
|
{
|
||||||
|
fn: (previous: BitbucketConnectionConfig) => ({
|
||||||
|
...previous,
|
||||||
|
url: previous.url ?? "https://bitbucket.example.com",
|
||||||
|
}),
|
||||||
|
name: "Set url to Bitbucket DC instance",
|
||||||
|
selectionText: "https://bitbucket.example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fn: (previous: BitbucketConnectionConfig) => ({
|
||||||
|
...previous,
|
||||||
|
repos: [
|
||||||
|
...(previous.repos ?? []),
|
||||||
|
"myProject/myRepo"
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
name: "Add a repo",
|
||||||
|
selectionText: "myProject/myRepo",
|
||||||
|
description: (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span>Add a individual repository to sync with. Ensure the repository is visible to the provided <Code>token</Code> (if any).</span>
|
||||||
|
<span className="text-sm mt-2 mb-1">Examples:</span>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
{[
|
||||||
|
"PROJ/repo-name",
|
||||||
|
"MYPROJ/api"
|
||||||
|
].map((repo) => (
|
||||||
|
<Code key={repo}>{repo}</Code>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fn: (previous: BitbucketConnectionConfig) => ({
|
||||||
|
...previous,
|
||||||
|
projects: [
|
||||||
|
...(previous.projects ?? []),
|
||||||
|
"myProject"
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
name: "Add a project",
|
||||||
|
selectionText: "myProject",
|
||||||
|
description: (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span>Add a project to sync with. Ensure the project is visible to the provided <Code>token</Code> (if any).</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fn: (previous: BitbucketConnectionConfig) => ({
|
||||||
|
...previous,
|
||||||
|
exclude: {
|
||||||
|
...previous.exclude,
|
||||||
|
repos: [...(previous.exclude?.repos ?? []), "myProject/myExcludedRepo"]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
name: "Exclude a repo",
|
||||||
|
selectionText: "myProject/myExcludedRepo",
|
||||||
|
description: (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span>Exclude a repository from syncing. Glob patterns are supported.</span>
|
||||||
|
<span className="text-sm mt-2 mb-1">Examples:</span>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
{[
|
||||||
|
"myProject/myExcludedRepo",
|
||||||
|
"myProject2/*"
|
||||||
|
].map((repo) => (
|
||||||
|
<Code key={repo}>{repo}</Code>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
// exclude archived
|
||||||
|
{
|
||||||
|
fn: (previous: BitbucketConnectionConfig) => ({
|
||||||
|
...previous,
|
||||||
|
exclude: {
|
||||||
|
...previous.exclude,
|
||||||
|
archived: true
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
name: "Exclude archived repos",
|
||||||
|
},
|
||||||
|
// exclude forked
|
||||||
|
{
|
||||||
|
fn: (previous: BitbucketConnectionConfig) => ({
|
||||||
|
...previous,
|
||||||
|
exclude: {
|
||||||
|
...previous.exclude,
|
||||||
|
forks: true
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
name: "Exclude forked repos",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ import {
|
||||||
GitHubConnectionCreationForm,
|
GitHubConnectionCreationForm,
|
||||||
GitLabConnectionCreationForm,
|
GitLabConnectionCreationForm,
|
||||||
GiteaConnectionCreationForm,
|
GiteaConnectionCreationForm,
|
||||||
GerritConnectionCreationForm
|
GerritConnectionCreationForm,
|
||||||
|
BitbucketCloudConnectionCreationForm,
|
||||||
|
BitbucketDataCenterConnectionCreationForm
|
||||||
} from "@/app/[domain]/components/connectionCreationForms";
|
} from "@/app/[domain]/components/connectionCreationForms";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
|
@ -79,6 +81,24 @@ export const ConnectCodeHost = ({ nextStep, securityCardEnabled }: ConnectCodeHo
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedCodeHost === "bitbucket-cloud") {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BackButton onClick={onBack} />
|
||||||
|
<BitbucketCloudConnectionCreationForm onCreated={onCreated} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedCodeHost === "bitbucket-server") {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BackButton onClick={onBack} />
|
||||||
|
<BitbucketDataCenterConnectionCreationForm onCreated={onCreated} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,7 +110,7 @@ const CodeHostSelection = ({ onSelect }: CodeHostSelectionProps) => {
|
||||||
const captureEvent = useCaptureEvent();
|
const captureEvent = useCaptureEvent();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6 max-w-3xl mx-auto">
|
||||||
<CodeHostIconButton
|
<CodeHostIconButton
|
||||||
name="GitHub"
|
name="GitHub"
|
||||||
logo={getCodeHostIcon("github")!}
|
logo={getCodeHostIcon("github")!}
|
||||||
|
|
@ -107,6 +127,22 @@ const CodeHostSelection = ({ onSelect }: CodeHostSelectionProps) => {
|
||||||
captureEvent("wa_onboard_gitlab_selected", {});
|
captureEvent("wa_onboard_gitlab_selected", {});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<CodeHostIconButton
|
||||||
|
name="Bitbucket Cloud"
|
||||||
|
logo={getCodeHostIcon("bitbucket-cloud")!}
|
||||||
|
onClick={() => {
|
||||||
|
onSelect("bitbucket-cloud");
|
||||||
|
captureEvent("wa_onboard_bitbucket_cloud_selected", {});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<CodeHostIconButton
|
||||||
|
name="Bitbucket DC"
|
||||||
|
logo={getCodeHostIcon("bitbucket-server")!}
|
||||||
|
onClick={() => {
|
||||||
|
onSelect("bitbucket-server");
|
||||||
|
captureEvent("wa_onboard_bitbucket_server_selected", {});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<CodeHostIconButton
|
<CodeHostIconButton
|
||||||
name="Gitea"
|
name="Gitea"
|
||||||
logo={getCodeHostIcon("gitea")!}
|
logo={getCodeHostIcon("gitea")!}
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,11 @@ export const CodePreviewPanel = ({
|
||||||
})
|
})
|
||||||
.join("/");
|
.join("/");
|
||||||
|
|
||||||
const optionalQueryParams = template.substring(template.indexOf("}}") + 2);
|
const optionalQueryParams =
|
||||||
|
template.substring(template.indexOf("}}") + 2)
|
||||||
|
.replace("{{.Version}}", branch ?? "HEAD")
|
||||||
|
.replace("{{.Path}}", fileMatch.FileName);
|
||||||
|
|
||||||
return url + optionalQueryParams;
|
return url + optionalQueryParams;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,8 @@ export type PosthogEventMap = {
|
||||||
wa_onboard_gitlab_selected: {},
|
wa_onboard_gitlab_selected: {},
|
||||||
wa_onboard_gitea_selected: {},
|
wa_onboard_gitea_selected: {},
|
||||||
wa_onboard_gerrit_selected: {},
|
wa_onboard_gerrit_selected: {},
|
||||||
|
wa_onboard_bitbucket_cloud_selected: {},
|
||||||
|
wa_onboard_bitbucket_server_selected: {},
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
wa_security_page_click: {},
|
wa_security_page_click: {},
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import githubLogo from "@/public/github.svg";
|
||||||
import gitlabLogo from "@/public/gitlab.svg";
|
import gitlabLogo from "@/public/gitlab.svg";
|
||||||
import giteaLogo from "@/public/gitea.svg";
|
import giteaLogo from "@/public/gitea.svg";
|
||||||
import gerritLogo from "@/public/gerrit.svg";
|
import gerritLogo from "@/public/gerrit.svg";
|
||||||
|
import bitbucketLogo from "@/public/bitbucket.svg";
|
||||||
import { ServiceError } from "./serviceError";
|
import { ServiceError } from "./serviceError";
|
||||||
import { Repository, RepositoryQuery } from "./types";
|
import { Repository, RepositoryQuery } from "./types";
|
||||||
|
|
||||||
|
|
@ -31,7 +32,7 @@ export const createPathWithQueryParams = (path: string, ...queryParams: [string,
|
||||||
return `${path}?${queryString}`;
|
return `${path}?${queryString}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CodeHostType = "github" | "gitlab" | "gitea" | "gerrit";
|
export type CodeHostType = "github" | "gitlab" | "gitea" | "gerrit" | "bitbucket-cloud" | "bitbucket-server";
|
||||||
|
|
||||||
type CodeHostInfo = {
|
type CodeHostInfo = {
|
||||||
type: CodeHostType;
|
type: CodeHostType;
|
||||||
|
|
@ -110,6 +111,28 @@ const _getCodeHostInfoInternal = (type: string, displayName: string, cloneUrl: s
|
||||||
iconClassName: className,
|
iconClassName: className,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "bitbucket-server": {
|
||||||
|
const { src, className } = getCodeHostIcon('bitbucket-server')!;
|
||||||
|
return {
|
||||||
|
type: "bitbucket-server",
|
||||||
|
displayName: displayName,
|
||||||
|
codeHostName: "Bitbucket",
|
||||||
|
repoLink: cloneUrl,
|
||||||
|
icon: src,
|
||||||
|
iconClassName: className,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "bitbucket-cloud": {
|
||||||
|
const { src, className } = getCodeHostIcon('bitbucket-cloud')!;
|
||||||
|
return {
|
||||||
|
type: "bitbucket-cloud",
|
||||||
|
displayName: displayName,
|
||||||
|
codeHostName: "Bitbucket",
|
||||||
|
repoLink: cloneUrl,
|
||||||
|
icon: src,
|
||||||
|
iconClassName: className,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,6 +155,11 @@ export const getCodeHostIcon = (codeHostType: CodeHostType): { src: string, clas
|
||||||
return {
|
return {
|
||||||
src: gerritLogo,
|
src: gerritLogo,
|
||||||
}
|
}
|
||||||
|
case "bitbucket-cloud":
|
||||||
|
case "bitbucket-server":
|
||||||
|
return {
|
||||||
|
src: bitbucketLogo,
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -142,6 +170,8 @@ export const isAuthSupportedForCodeHost = (codeHostType: CodeHostType): boolean
|
||||||
case "github":
|
case "github":
|
||||||
case "gitlab":
|
case "gitlab":
|
||||||
case "gitea":
|
case "gitea":
|
||||||
|
case "bitbucket-cloud":
|
||||||
|
case "bitbucket-server":
|
||||||
return true;
|
return true;
|
||||||
case "gerrit":
|
case "gerrit":
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
105
schemas/v3/bitbucket.json
Normal file
105
schemas/v3/bitbucket.json
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"title": "BitbucketConnectionConfig",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "bitbucket",
|
||||||
|
"description": "Bitbucket configuration"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The username to use for authentication. Only needed if token is an app password."
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"$ref": "./shared.json#/definitions/Token",
|
||||||
|
"description": "An authentication token.",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"secret": "SECRET_KEY"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "url",
|
||||||
|
"default": "https://api.bitbucket.org/2.0",
|
||||||
|
"description": "Bitbucket URL",
|
||||||
|
"examples": [
|
||||||
|
"https://bitbucket.example.com"
|
||||||
|
],
|
||||||
|
"pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$"
|
||||||
|
},
|
||||||
|
"deploymentType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["cloud", "server"],
|
||||||
|
"default": "cloud",
|
||||||
|
"description": "The type of Bitbucket deployment"
|
||||||
|
},
|
||||||
|
"workspaces": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of workspaces to sync. Ignored if deploymentType is server."
|
||||||
|
},
|
||||||
|
"projects": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of projects to sync"
|
||||||
|
},
|
||||||
|
"repos": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of repos to sync"
|
||||||
|
},
|
||||||
|
"exclude": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"archived": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Exclude archived repositories from syncing."
|
||||||
|
},
|
||||||
|
"forks": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Exclude forked repositories from syncing."
|
||||||
|
},
|
||||||
|
"repos": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
[
|
||||||
|
"cloud_workspace/repo1",
|
||||||
|
"server_project/repo2"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"description": "List of specific repos to exclude from syncing."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"revisions": {
|
||||||
|
"$ref": "./shared.json#/definitions/GitRevisions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"deploymentType": { "const": "server" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"required": ["url"]
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,9 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "./gerrit.json"
|
"$ref": "./gerrit.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "./bitbucket.json"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
28
yarn.lock
28
yarn.lock
|
|
@ -667,6 +667,17 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@coderabbitai/bitbucket@npm:^1.1.3":
|
||||||
|
version: 1.1.3
|
||||||
|
resolution: "@coderabbitai/bitbucket@npm:1.1.3"
|
||||||
|
dependencies:
|
||||||
|
openapi-fetch: "npm:^0.13.4"
|
||||||
|
bin:
|
||||||
|
coderabbitai-bitbucket: dist/main.js
|
||||||
|
checksum: 10c0/0c034866f8094b9ce68a5292d513fe6226d50b333a0a6ce929542b635a8bcf1d8d5abc7eb1aaf665ebce99e312a192d83b224085843d5641806ca4adc36ab0ff
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@colors/colors@npm:1.6.0, @colors/colors@npm:^1.6.0":
|
"@colors/colors@npm:1.6.0, @colors/colors@npm:^1.6.0":
|
||||||
version: 1.6.0
|
version: 1.6.0
|
||||||
resolution: "@colors/colors@npm:1.6.0"
|
resolution: "@colors/colors@npm:1.6.0"
|
||||||
|
|
@ -11611,6 +11622,22 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"openapi-fetch@npm:^0.13.4":
|
||||||
|
version: 0.13.5
|
||||||
|
resolution: "openapi-fetch@npm:0.13.5"
|
||||||
|
dependencies:
|
||||||
|
openapi-typescript-helpers: "npm:^0.0.15"
|
||||||
|
checksum: 10c0/57736d9d4310d7bc7fa5e4e37e80d28893a7fefee88ee6e0327600de893e0638479445bf0c9f5bd7b1a2429f409425d3945d6e942b23b37b8081630ac52244fb
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"openapi-typescript-helpers@npm:^0.0.15":
|
||||||
|
version: 0.0.15
|
||||||
|
resolution: "openapi-typescript-helpers@npm:0.0.15"
|
||||||
|
checksum: 10c0/5eb68d487b787e3e31266470b1a310726549dd45a1079655ab18066ab291b0b3c343fdf629991013706a2329b86964f8798d56ef0272b94b931fe6c19abd7a88
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"optionator@npm:^0.9.3":
|
"optionator@npm:^0.9.3":
|
||||||
version: 0.9.4
|
version: 0.9.4
|
||||||
resolution: "optionator@npm:0.9.4"
|
resolution: "optionator@npm:0.9.4"
|
||||||
|
|
@ -13047,6 +13074,7 @@ __metadata:
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "root-workspace-0b6124@workspace:."
|
resolution: "root-workspace-0b6124@workspace:."
|
||||||
dependencies:
|
dependencies:
|
||||||
|
"@coderabbitai/bitbucket": "npm:^1.1.3"
|
||||||
cross-env: "npm:^7.0.3"
|
cross-env: "npm:^7.0.3"
|
||||||
dotenv-cli: "npm:^8.0.0"
|
dotenv-cli: "npm:^8.0.0"
|
||||||
npm-run-all: "npm:^4.1.5"
|
npm-run-all: "npm:^4.1.5"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue