@rawdash/connector-bitbucket 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -0
- package/dist/index.d.ts +359 -0
- package/dist/index.js +597 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<!-- This file is generated from connector metadata by scripts/generate-connector-docs.ts. Do not edit by hand. -->
|
|
2
|
+
|
|
3
|
+
# @rawdash/connector-bitbucket
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@rawdash/connector-bitbucket)
|
|
6
|
+
[](https://github.com/rawdash/rawdash/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
Sync pull requests, pipelines, and pipeline lifecycle events from Bitbucket Cloud repositories.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @rawdash/connector-bitbucket
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Authentication
|
|
17
|
+
|
|
18
|
+
Authenticates over HTTP Basic auth using an Atlassian account username and a Bitbucket app password. The password is scoped to the projects and repositories the account can already read.
|
|
19
|
+
|
|
20
|
+
1. Open Bitbucket -> Personal settings -> App passwords (https://bitbucket.org/account/settings/app-passwords/).
|
|
21
|
+
2. Create an app password with `Repositories:Read` and `Pipelines:Read` scopes.
|
|
22
|
+
3. Store it as a secret and reference it from the connector config as `appPassword: secret("BITBUCKET_APP_PASSWORD")`, alongside your `workspace`, `username`, and the list of `repoSlugs` to sync.
|
|
23
|
+
|
|
24
|
+
## Configuration
|
|
25
|
+
|
|
26
|
+
| Field | Type | Required | Description |
|
|
27
|
+
| ------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
28
|
+
| `workspace` | string | Yes | Bitbucket Cloud workspace slug (the segment shown in repo URLs after bitbucket.org/). |
|
|
29
|
+
| `username` | string | Yes | Atlassian account username paired with the app password for Basic auth (find it under Personal settings -> Account settings). |
|
|
30
|
+
| `appPassword` | secret | Yes | Bitbucket app password with `Repositories:Read` and `Pipelines:Read` scopes. Create one at Personal settings -> App passwords. |
|
|
31
|
+
| `repoSlugs` | array | Yes | Repositories to sync, named by their slug within the workspace (no `workspace/` prefix). |
|
|
32
|
+
| `resources` | array | No | Which Bitbucket resources to sync. Omit to sync all of them. 'pipeline_event' rides the 'pipeline' phase - enabling it without 'pipeline' still fetches pipelines but skips writing pipeline entities. |
|
|
33
|
+
|
|
34
|
+
## Resources
|
|
35
|
+
|
|
36
|
+
- **`pull_request`** _(entity)_ - Open, merged, declined, and superseded pull requests with author, source/target branches, and close timestamp.
|
|
37
|
+
- Endpoint: `GET /2.0/repositories/{workspace}/{repo_slug}/pullrequests?state=OPEN,MERGED,DECLINED,SUPERSEDED`
|
|
38
|
+
- Paginated newest-first by `updated_on`; the connector stops once a page is entirely older than `options.since`.
|
|
39
|
+
- **`pipeline`** _(entity)_ - Bitbucket Pipelines runs with state, result, target ref/commit, trigger, duration, and create/complete timestamps.
|
|
40
|
+
- Endpoint: `GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/`
|
|
41
|
+
- Paginated newest-first by `created_on`; the connector stops once a page is entirely older than `options.since`.
|
|
42
|
+
- **`pipeline_event`** _(event)_ - Pipeline lifecycle events. One event per pipeline covering created_on to completed_on (or updated_on if not yet finished), tagged with the terminal state and result.
|
|
43
|
+
- Endpoint: `GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/`
|
|
44
|
+
- Derived from the same pipelines response that builds the `pipeline` resource; the Bitbucket API does not expose an intermediate state-transition history endpoint.
|
|
45
|
+
|
|
46
|
+
## Example
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import {
|
|
50
|
+
defineConfig,
|
|
51
|
+
defineDashboard,
|
|
52
|
+
defineMetric,
|
|
53
|
+
secret,
|
|
54
|
+
} from '@rawdash/core';
|
|
55
|
+
|
|
56
|
+
const bitbucket = {
|
|
57
|
+
name: 'bitbucket',
|
|
58
|
+
connectorId: 'bitbucket',
|
|
59
|
+
config: {
|
|
60
|
+
workspace: 'my-workspace',
|
|
61
|
+
username: 'janedoe',
|
|
62
|
+
appPassword: secret('BITBUCKET_APP_PASSWORD'),
|
|
63
|
+
repoSlugs: ['my-repo'],
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default defineConfig({
|
|
68
|
+
connectors: [bitbucket],
|
|
69
|
+
dashboards: {
|
|
70
|
+
engineering: defineDashboard({
|
|
71
|
+
widgets: {
|
|
72
|
+
open_pull_requests: {
|
|
73
|
+
kind: 'stat',
|
|
74
|
+
title: 'Open PRs',
|
|
75
|
+
metric: defineMetric({
|
|
76
|
+
connector: bitbucket,
|
|
77
|
+
shape: 'entity',
|
|
78
|
+
entityType: 'pull_request',
|
|
79
|
+
fn: 'count',
|
|
80
|
+
filter: [{ field: 'state', op: 'eq', value: 'OPEN' }],
|
|
81
|
+
}),
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Rate limits
|
|
90
|
+
|
|
91
|
+
Bitbucket Cloud applies hourly per-IP and per-user limits (around 1,000 requests/hour for app-password auth). Pagination uses a `next` URL in each response and a configurable `pagelen` (capped at 50 here).
|
|
92
|
+
|
|
93
|
+
## Limitations
|
|
94
|
+
|
|
95
|
+
- Bitbucket Server / Data Center are out of scope; this connector targets Bitbucket Cloud only.
|
|
96
|
+
- Pipeline state-transition events are synthesized: one `pipeline_event` is emitted per pipeline lifecycle (created_on to completed_on/updated_on), not one per intermediate state change.
|
|
97
|
+
- Repository discovery is not automatic - configure each repository slug explicitly via `repoSlugs`.
|
|
98
|
+
|
|
99
|
+
## Links
|
|
100
|
+
|
|
101
|
+
- [Rawdash docs](https://rawdash.dev/docs/connectors/)
|
|
102
|
+
- [Atlassian API docs](https://developer.atlassian.com/cloud/bitbucket/rest/intro/)
|
|
103
|
+
- [GitHub](https://github.com/rawdash/rawdash)
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
Apache-2.0
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { BaseConnector, ConnectorContext, SyncOptions, StorageHandle, SyncResult, ConnectorDoc } from '@rawdash/core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
declare const configFields: z.ZodObject<{
|
|
5
|
+
workspace: z.ZodString;
|
|
6
|
+
username: z.ZodString;
|
|
7
|
+
appPassword: z.ZodObject<{
|
|
8
|
+
$secret: z.ZodString;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
repoSlugs: z.ZodArray<z.ZodString>;
|
|
11
|
+
resources: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
12
|
+
pipeline: "pipeline";
|
|
13
|
+
pull_request: "pull_request";
|
|
14
|
+
pipeline_event: "pipeline_event";
|
|
15
|
+
}>>>;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
declare const doc: ConnectorDoc;
|
|
18
|
+
type BitbucketResource = 'pull_request' | 'pipeline' | 'pipeline_event';
|
|
19
|
+
interface BitbucketSettings {
|
|
20
|
+
workspace: string;
|
|
21
|
+
repoSlugs: readonly string[];
|
|
22
|
+
resources?: readonly BitbucketResource[];
|
|
23
|
+
}
|
|
24
|
+
declare const bitbucketCredentials: {
|
|
25
|
+
username: {
|
|
26
|
+
description: string;
|
|
27
|
+
auth: "required";
|
|
28
|
+
};
|
|
29
|
+
appPassword: {
|
|
30
|
+
description: string;
|
|
31
|
+
auth: "required";
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
type BitbucketCredentials = typeof bitbucketCredentials;
|
|
35
|
+
declare const bitbucketResources: {
|
|
36
|
+
readonly pull_request: {
|
|
37
|
+
readonly shape: "entity";
|
|
38
|
+
readonly description: "Open, merged, declined, and superseded pull requests with author, source/target branches, and close timestamp.";
|
|
39
|
+
readonly endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pullrequests?state=OPEN,MERGED,DECLINED,SUPERSEDED";
|
|
40
|
+
readonly notes: "Paginated newest-first by `updated_on`; the connector stops once a page is entirely older than `options.since`.";
|
|
41
|
+
readonly responses: {
|
|
42
|
+
readonly pull_requests: z.ZodObject<{
|
|
43
|
+
values: z.ZodArray<z.ZodObject<{
|
|
44
|
+
id: z.ZodNumber;
|
|
45
|
+
title: z.ZodString;
|
|
46
|
+
state: z.ZodString;
|
|
47
|
+
author: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
48
|
+
uuid: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
49
|
+
display_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
50
|
+
nickname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
51
|
+
account_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
52
|
+
}, z.core.$strip>>>;
|
|
53
|
+
source: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
54
|
+
branch: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
55
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
56
|
+
}, z.core.$strip>>>;
|
|
57
|
+
commit: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
58
|
+
hash: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
59
|
+
}, z.core.$strip>>>;
|
|
60
|
+
}, z.core.$strip>>>;
|
|
61
|
+
destination: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
62
|
+
branch: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
63
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
64
|
+
}, z.core.$strip>>>;
|
|
65
|
+
commit: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
66
|
+
hash: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
67
|
+
}, z.core.$strip>>>;
|
|
68
|
+
}, z.core.$strip>>>;
|
|
69
|
+
created_on: z.ZodISODateTime;
|
|
70
|
+
updated_on: z.ZodISODateTime;
|
|
71
|
+
closed_on: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
|
|
72
|
+
links: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
73
|
+
html: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
74
|
+
href: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
75
|
+
}, z.core.$strip>>>;
|
|
76
|
+
}, z.core.$strip>>>;
|
|
77
|
+
}, z.core.$strip>>;
|
|
78
|
+
next: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
79
|
+
page: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
80
|
+
pagelen: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
81
|
+
}, z.core.$strip>;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
readonly pipeline: {
|
|
85
|
+
readonly shape: "entity";
|
|
86
|
+
readonly description: "Bitbucket Pipelines runs with state, result, target ref/commit, trigger, duration, and create/complete timestamps.";
|
|
87
|
+
readonly endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/";
|
|
88
|
+
readonly notes: "Paginated newest-first by `created_on`; the connector stops once a page is entirely older than `options.since`.";
|
|
89
|
+
readonly responses: {
|
|
90
|
+
readonly pipelines: z.ZodObject<{
|
|
91
|
+
values: z.ZodArray<z.ZodObject<{
|
|
92
|
+
uuid: z.ZodString;
|
|
93
|
+
build_number: z.ZodNumber;
|
|
94
|
+
state: z.ZodObject<{
|
|
95
|
+
name: z.ZodString;
|
|
96
|
+
type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
97
|
+
result: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
98
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
99
|
+
}, z.core.$strip>>>;
|
|
100
|
+
}, z.core.$strip>;
|
|
101
|
+
creator: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
102
|
+
uuid: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
103
|
+
display_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
104
|
+
nickname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
105
|
+
account_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
106
|
+
}, z.core.$strip>>>;
|
|
107
|
+
target: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
108
|
+
ref_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
109
|
+
commit: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
110
|
+
hash: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
111
|
+
}, z.core.$strip>>>;
|
|
112
|
+
selector: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
113
|
+
type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
114
|
+
pattern: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
115
|
+
}, z.core.$strip>>>;
|
|
116
|
+
}, z.core.$strip>>>;
|
|
117
|
+
trigger: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
118
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
119
|
+
type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
120
|
+
}, z.core.$strip>>>;
|
|
121
|
+
created_on: z.ZodISODateTime;
|
|
122
|
+
completed_on: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
|
|
123
|
+
duration_in_seconds: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
124
|
+
build_seconds_used: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
125
|
+
}, z.core.$strip>>;
|
|
126
|
+
next: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
127
|
+
page: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
128
|
+
pagelen: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
129
|
+
}, z.core.$strip>;
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
readonly pipeline_event: {
|
|
133
|
+
readonly shape: "event";
|
|
134
|
+
readonly description: "Pipeline lifecycle events. One event per pipeline covering created_on to completed_on (or updated_on if not yet finished), tagged with the terminal state and result.";
|
|
135
|
+
readonly endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/";
|
|
136
|
+
readonly notes: "Derived from the same pipelines response that builds the `pipeline` resource; the Bitbucket API does not expose an intermediate state-transition history endpoint.";
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
declare const id = "bitbucket";
|
|
140
|
+
declare class BitbucketConnector extends BaseConnector<BitbucketSettings, BitbucketCredentials> {
|
|
141
|
+
static readonly id = "bitbucket";
|
|
142
|
+
static readonly resources: {
|
|
143
|
+
readonly pull_request: {
|
|
144
|
+
readonly shape: "entity";
|
|
145
|
+
readonly description: "Open, merged, declined, and superseded pull requests with author, source/target branches, and close timestamp.";
|
|
146
|
+
readonly endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pullrequests?state=OPEN,MERGED,DECLINED,SUPERSEDED";
|
|
147
|
+
readonly notes: "Paginated newest-first by `updated_on`; the connector stops once a page is entirely older than `options.since`.";
|
|
148
|
+
readonly responses: {
|
|
149
|
+
readonly pull_requests: z.ZodObject<{
|
|
150
|
+
values: z.ZodArray<z.ZodObject<{
|
|
151
|
+
id: z.ZodNumber;
|
|
152
|
+
title: z.ZodString;
|
|
153
|
+
state: z.ZodString;
|
|
154
|
+
author: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
155
|
+
uuid: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
156
|
+
display_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
157
|
+
nickname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
158
|
+
account_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
159
|
+
}, z.core.$strip>>>;
|
|
160
|
+
source: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
161
|
+
branch: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
162
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
163
|
+
}, z.core.$strip>>>;
|
|
164
|
+
commit: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
165
|
+
hash: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
166
|
+
}, z.core.$strip>>>;
|
|
167
|
+
}, z.core.$strip>>>;
|
|
168
|
+
destination: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
169
|
+
branch: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
170
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
171
|
+
}, z.core.$strip>>>;
|
|
172
|
+
commit: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
173
|
+
hash: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
174
|
+
}, z.core.$strip>>>;
|
|
175
|
+
}, z.core.$strip>>>;
|
|
176
|
+
created_on: z.ZodISODateTime;
|
|
177
|
+
updated_on: z.ZodISODateTime;
|
|
178
|
+
closed_on: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
|
|
179
|
+
links: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
180
|
+
html: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
181
|
+
href: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
182
|
+
}, z.core.$strip>>>;
|
|
183
|
+
}, z.core.$strip>>>;
|
|
184
|
+
}, z.core.$strip>>;
|
|
185
|
+
next: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
186
|
+
page: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
187
|
+
pagelen: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
188
|
+
}, z.core.$strip>;
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
readonly pipeline: {
|
|
192
|
+
readonly shape: "entity";
|
|
193
|
+
readonly description: "Bitbucket Pipelines runs with state, result, target ref/commit, trigger, duration, and create/complete timestamps.";
|
|
194
|
+
readonly endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/";
|
|
195
|
+
readonly notes: "Paginated newest-first by `created_on`; the connector stops once a page is entirely older than `options.since`.";
|
|
196
|
+
readonly responses: {
|
|
197
|
+
readonly pipelines: z.ZodObject<{
|
|
198
|
+
values: z.ZodArray<z.ZodObject<{
|
|
199
|
+
uuid: z.ZodString;
|
|
200
|
+
build_number: z.ZodNumber;
|
|
201
|
+
state: z.ZodObject<{
|
|
202
|
+
name: z.ZodString;
|
|
203
|
+
type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
204
|
+
result: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
205
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
206
|
+
}, z.core.$strip>>>;
|
|
207
|
+
}, z.core.$strip>;
|
|
208
|
+
creator: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
209
|
+
uuid: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
210
|
+
display_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
211
|
+
nickname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
212
|
+
account_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
213
|
+
}, z.core.$strip>>>;
|
|
214
|
+
target: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
215
|
+
ref_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
216
|
+
commit: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
217
|
+
hash: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
218
|
+
}, z.core.$strip>>>;
|
|
219
|
+
selector: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
220
|
+
type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
221
|
+
pattern: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
222
|
+
}, z.core.$strip>>>;
|
|
223
|
+
}, z.core.$strip>>>;
|
|
224
|
+
trigger: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
225
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
226
|
+
type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
227
|
+
}, z.core.$strip>>>;
|
|
228
|
+
created_on: z.ZodISODateTime;
|
|
229
|
+
completed_on: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
|
|
230
|
+
duration_in_seconds: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
231
|
+
build_seconds_used: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
232
|
+
}, z.core.$strip>>;
|
|
233
|
+
next: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
234
|
+
page: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
235
|
+
pagelen: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
236
|
+
}, z.core.$strip>;
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
readonly pipeline_event: {
|
|
240
|
+
readonly shape: "event";
|
|
241
|
+
readonly description: "Pipeline lifecycle events. One event per pipeline covering created_on to completed_on (or updated_on if not yet finished), tagged with the terminal state and result.";
|
|
242
|
+
readonly endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/";
|
|
243
|
+
readonly notes: "Derived from the same pipelines response that builds the `pipeline` resource; the Bitbucket API does not expose an intermediate state-transition history endpoint.";
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
static readonly schemas: object & {
|
|
247
|
+
readonly pull_requests: z.ZodObject<{
|
|
248
|
+
values: z.ZodArray<z.ZodObject<{
|
|
249
|
+
id: z.ZodNumber;
|
|
250
|
+
title: z.ZodString;
|
|
251
|
+
state: z.ZodString;
|
|
252
|
+
author: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
253
|
+
uuid: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
254
|
+
display_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
255
|
+
nickname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
256
|
+
account_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
257
|
+
}, z.core.$strip>>>;
|
|
258
|
+
source: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
259
|
+
branch: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
260
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
261
|
+
}, z.core.$strip>>>;
|
|
262
|
+
commit: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
263
|
+
hash: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
264
|
+
}, z.core.$strip>>>;
|
|
265
|
+
}, z.core.$strip>>>;
|
|
266
|
+
destination: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
267
|
+
branch: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
268
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
269
|
+
}, z.core.$strip>>>;
|
|
270
|
+
commit: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
271
|
+
hash: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
272
|
+
}, z.core.$strip>>>;
|
|
273
|
+
}, z.core.$strip>>>;
|
|
274
|
+
created_on: z.ZodISODateTime;
|
|
275
|
+
updated_on: z.ZodISODateTime;
|
|
276
|
+
closed_on: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
|
|
277
|
+
links: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
278
|
+
html: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
279
|
+
href: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
280
|
+
}, z.core.$strip>>>;
|
|
281
|
+
}, z.core.$strip>>>;
|
|
282
|
+
}, z.core.$strip>>;
|
|
283
|
+
next: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
284
|
+
page: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
285
|
+
pagelen: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
286
|
+
}, z.core.$strip>;
|
|
287
|
+
} & {
|
|
288
|
+
readonly pipelines: z.ZodObject<{
|
|
289
|
+
values: z.ZodArray<z.ZodObject<{
|
|
290
|
+
uuid: z.ZodString;
|
|
291
|
+
build_number: z.ZodNumber;
|
|
292
|
+
state: z.ZodObject<{
|
|
293
|
+
name: z.ZodString;
|
|
294
|
+
type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
295
|
+
result: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
296
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
297
|
+
}, z.core.$strip>>>;
|
|
298
|
+
}, z.core.$strip>;
|
|
299
|
+
creator: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
300
|
+
uuid: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
301
|
+
display_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
302
|
+
nickname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
303
|
+
account_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
304
|
+
}, z.core.$strip>>>;
|
|
305
|
+
target: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
306
|
+
ref_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
307
|
+
commit: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
308
|
+
hash: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
309
|
+
}, z.core.$strip>>>;
|
|
310
|
+
selector: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
311
|
+
type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
312
|
+
pattern: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
313
|
+
}, z.core.$strip>>>;
|
|
314
|
+
}, z.core.$strip>>>;
|
|
315
|
+
trigger: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
316
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
317
|
+
type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
318
|
+
}, z.core.$strip>>>;
|
|
319
|
+
created_on: z.ZodISODateTime;
|
|
320
|
+
completed_on: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
|
|
321
|
+
duration_in_seconds: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
322
|
+
build_seconds_used: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
323
|
+
}, z.core.$strip>>;
|
|
324
|
+
next: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
325
|
+
page: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
326
|
+
pagelen: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
327
|
+
}, z.core.$strip>;
|
|
328
|
+
} & Readonly<Record<string, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>>;
|
|
329
|
+
static create(input: unknown, ctx?: ConnectorContext): BitbucketConnector;
|
|
330
|
+
readonly id = "bitbucket";
|
|
331
|
+
readonly credentials: {
|
|
332
|
+
username: {
|
|
333
|
+
description: string;
|
|
334
|
+
auth: "required";
|
|
335
|
+
};
|
|
336
|
+
appPassword: {
|
|
337
|
+
description: string;
|
|
338
|
+
auth: "required";
|
|
339
|
+
};
|
|
340
|
+
};
|
|
341
|
+
private buildHeaders;
|
|
342
|
+
private fetch;
|
|
343
|
+
private sanitizeUrl;
|
|
344
|
+
private static readonly PHASE_RESOURCES;
|
|
345
|
+
private activePhases;
|
|
346
|
+
private isResourceAllowed;
|
|
347
|
+
private buildPullRequestsUrl;
|
|
348
|
+
private buildPipelinesUrl;
|
|
349
|
+
private pullRequestsPath;
|
|
350
|
+
private pipelinesPath;
|
|
351
|
+
private fetchPullRequestsPage;
|
|
352
|
+
private fetchPipelinesPage;
|
|
353
|
+
private writePullRequests;
|
|
354
|
+
private writePipelines;
|
|
355
|
+
private resolveCursor;
|
|
356
|
+
sync(options: SyncOptions, storage: StorageHandle, signal?: AbortSignal): Promise<SyncResult>;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export { BitbucketConnector, type BitbucketResource, type BitbucketSettings, configFields, BitbucketConnector as default, doc, id, bitbucketResources as resources };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
// ../../connector-shared/dist/index.js
|
|
2
|
+
var HTTP_CLIENT_VERSION = "0.0.0";
|
|
3
|
+
var DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;
|
|
4
|
+
function connectorUserAgent(connectorId) {
|
|
5
|
+
return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;
|
|
6
|
+
}
|
|
7
|
+
function parseEpoch(value, unit) {
|
|
8
|
+
if (value === null || value === void 0) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
if (unit === "iso") {
|
|
12
|
+
if (typeof value !== "string") {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const ms = new Date(value).getTime();
|
|
16
|
+
return Number.isFinite(ms) ? ms : null;
|
|
17
|
+
}
|
|
18
|
+
if (typeof value === "string" && value.trim() === "") {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
22
|
+
if (!Number.isFinite(n)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const result = unit === "s" ? n * 1e3 : n;
|
|
26
|
+
return Number.isFinite(result) ? result : null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/bitbucket.ts
|
|
30
|
+
import {
|
|
31
|
+
BaseConnector,
|
|
32
|
+
defineConfigFields,
|
|
33
|
+
defineConnectorDoc,
|
|
34
|
+
defineResources,
|
|
35
|
+
makeChunkedCursorGuard,
|
|
36
|
+
paginateChunked,
|
|
37
|
+
schemasFromResources,
|
|
38
|
+
selectActivePhases
|
|
39
|
+
} from "@rawdash/core";
|
|
40
|
+
import { z } from "zod";
|
|
41
|
+
var repoSlug = z.string().min(1).regex(
|
|
42
|
+
/^[^/\s?#]+$/,
|
|
43
|
+
"Use the repository slug only (no workspace prefix, slashes, or query)."
|
|
44
|
+
);
|
|
45
|
+
var configFields = defineConfigFields(
|
|
46
|
+
z.object({
|
|
47
|
+
workspace: z.string().min(1).regex(
|
|
48
|
+
/^[^/\s?#]+$/,
|
|
49
|
+
"Use the workspace slug only (no slashes, spaces, or query)."
|
|
50
|
+
).meta({
|
|
51
|
+
label: "Workspace",
|
|
52
|
+
description: "Bitbucket Cloud workspace slug (the segment shown in repo URLs after bitbucket.org/).",
|
|
53
|
+
placeholder: "my-workspace"
|
|
54
|
+
}),
|
|
55
|
+
username: z.string().min(1).meta({
|
|
56
|
+
label: "Atlassian username",
|
|
57
|
+
description: "Atlassian account username paired with the app password for Basic auth (find it under Personal settings -> Account settings).",
|
|
58
|
+
placeholder: "janedoe"
|
|
59
|
+
}),
|
|
60
|
+
appPassword: z.object({ $secret: z.string() }).meta({
|
|
61
|
+
label: "App password",
|
|
62
|
+
description: "Bitbucket app password with `Repositories:Read` and `Pipelines:Read` scopes. Create one at Personal settings -> App passwords.",
|
|
63
|
+
placeholder: "ATBB...",
|
|
64
|
+
secret: true
|
|
65
|
+
}),
|
|
66
|
+
repoSlugs: z.array(repoSlug).nonempty().refine((slugs) => new Set(slugs).size === slugs.length, {
|
|
67
|
+
error: "Repository slugs must be unique."
|
|
68
|
+
}).meta({
|
|
69
|
+
label: "Repository slugs",
|
|
70
|
+
description: "Repositories to sync, named by their slug within the workspace (no `workspace/` prefix)."
|
|
71
|
+
}),
|
|
72
|
+
resources: z.array(z.enum(["pull_request", "pipeline", "pipeline_event"])).nonempty().optional().meta({
|
|
73
|
+
label: "Resources",
|
|
74
|
+
description: "Which Bitbucket resources to sync. Omit to sync all of them. 'pipeline_event' rides the 'pipeline' phase - enabling it without 'pipeline' still fetches pipelines but skips writing pipeline entities."
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
var doc = defineConnectorDoc({
|
|
79
|
+
displayName: "Bitbucket",
|
|
80
|
+
category: "engineering",
|
|
81
|
+
brandColor: "#0052CC",
|
|
82
|
+
tagline: "Sync pull requests, pipelines, and pipeline lifecycle events from Bitbucket Cloud repositories.",
|
|
83
|
+
vendor: {
|
|
84
|
+
name: "Atlassian",
|
|
85
|
+
apiDocs: "https://developer.atlassian.com/cloud/bitbucket/rest/intro/",
|
|
86
|
+
website: "https://bitbucket.org"
|
|
87
|
+
},
|
|
88
|
+
auth: {
|
|
89
|
+
summary: "Authenticates over HTTP Basic auth using an Atlassian account username and a Bitbucket app password. The password is scoped to the projects and repositories the account can already read.",
|
|
90
|
+
setup: [
|
|
91
|
+
"Open Bitbucket -> Personal settings -> App passwords (https://bitbucket.org/account/settings/app-passwords/).",
|
|
92
|
+
"Create an app password with `Repositories:Read` and `Pipelines:Read` scopes.",
|
|
93
|
+
'Store it as a secret and reference it from the connector config as `appPassword: secret("BITBUCKET_APP_PASSWORD")`, alongside your `workspace`, `username`, and the list of `repoSlugs` to sync.'
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
rateLimit: "Bitbucket Cloud applies hourly per-IP and per-user limits (around 1,000 requests/hour for app-password auth). Pagination uses a `next` URL in each response and a configurable `pagelen` (capped at 50 here).",
|
|
97
|
+
limitations: [
|
|
98
|
+
"Bitbucket Server / Data Center are out of scope; this connector targets Bitbucket Cloud only.",
|
|
99
|
+
"Pipeline state-transition events are synthesized: one `pipeline_event` is emitted per pipeline lifecycle (created_on to completed_on/updated_on), not one per intermediate state change.",
|
|
100
|
+
"Repository discovery is not automatic - configure each repository slug explicitly via `repoSlugs`."
|
|
101
|
+
]
|
|
102
|
+
});
|
|
103
|
+
var bitbucketCredentials = {
|
|
104
|
+
username: {
|
|
105
|
+
description: "Atlassian account username",
|
|
106
|
+
auth: "required"
|
|
107
|
+
},
|
|
108
|
+
appPassword: {
|
|
109
|
+
description: "Bitbucket app password",
|
|
110
|
+
auth: "required"
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var API_HOST = "api.bitbucket.org";
|
|
114
|
+
var API_BASE = `https://${API_HOST}`;
|
|
115
|
+
var PAGE_SIZE = 50;
|
|
116
|
+
var PHASE_ORDER = ["pull_requests", "pipelines"];
|
|
117
|
+
var isBitbucketSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);
|
|
118
|
+
function decodePage(page) {
|
|
119
|
+
if (page === null) {
|
|
120
|
+
return { idx: 0, url: null };
|
|
121
|
+
}
|
|
122
|
+
const sep = page.indexOf("|");
|
|
123
|
+
if (sep === -1) {
|
|
124
|
+
return { idx: 0, url: null };
|
|
125
|
+
}
|
|
126
|
+
const idxRaw = Number.parseInt(page.slice(0, sep), 10);
|
|
127
|
+
const url = page.slice(sep + 1);
|
|
128
|
+
return {
|
|
129
|
+
idx: Number.isFinite(idxRaw) && idxRaw >= 0 ? idxRaw : 0,
|
|
130
|
+
url: url === "" ? null : url
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function encodePage(idx, url) {
|
|
134
|
+
return `${idx}|${url ?? ""}`;
|
|
135
|
+
}
|
|
136
|
+
var accountRefSchema = z.object({
|
|
137
|
+
uuid: z.string().nullable().optional(),
|
|
138
|
+
display_name: z.string().nullable().optional(),
|
|
139
|
+
nickname: z.string().nullable().optional(),
|
|
140
|
+
account_id: z.string().nullable().optional()
|
|
141
|
+
});
|
|
142
|
+
var branchRefSchema = z.object({
|
|
143
|
+
branch: z.object({ name: z.string().nullable().optional() }).nullable().optional(),
|
|
144
|
+
commit: z.object({ hash: z.string().nullable().optional() }).nullable().optional()
|
|
145
|
+
});
|
|
146
|
+
var pullRequestSchema = z.object({
|
|
147
|
+
id: z.number().int().nonnegative(),
|
|
148
|
+
title: z.string(),
|
|
149
|
+
state: z.string().min(1),
|
|
150
|
+
author: accountRefSchema.nullable().optional(),
|
|
151
|
+
source: branchRefSchema.nullable().optional(),
|
|
152
|
+
destination: branchRefSchema.nullable().optional(),
|
|
153
|
+
created_on: z.iso.datetime(),
|
|
154
|
+
updated_on: z.iso.datetime(),
|
|
155
|
+
closed_on: z.iso.datetime().nullable().optional(),
|
|
156
|
+
links: z.object({
|
|
157
|
+
html: z.object({ href: z.string().nullable().optional() }).nullable().optional()
|
|
158
|
+
}).nullable().optional()
|
|
159
|
+
});
|
|
160
|
+
var pullRequestsResponseSchema = z.object({
|
|
161
|
+
values: z.array(pullRequestSchema),
|
|
162
|
+
next: z.string().nullable().optional(),
|
|
163
|
+
page: z.number().int().nullable().optional(),
|
|
164
|
+
pagelen: z.number().int().nullable().optional()
|
|
165
|
+
});
|
|
166
|
+
var pipelineStateSchema = z.object({
|
|
167
|
+
name: z.string().min(1),
|
|
168
|
+
type: z.string().nullable().optional(),
|
|
169
|
+
result: z.object({ name: z.string().nullable().optional() }).nullable().optional()
|
|
170
|
+
});
|
|
171
|
+
var pipelineTargetSchema = z.object({
|
|
172
|
+
ref_name: z.string().nullable().optional(),
|
|
173
|
+
commit: z.object({ hash: z.string().nullable().optional() }).nullable().optional(),
|
|
174
|
+
selector: z.object({
|
|
175
|
+
type: z.string().nullable().optional(),
|
|
176
|
+
pattern: z.string().nullable().optional()
|
|
177
|
+
}).nullable().optional()
|
|
178
|
+
});
|
|
179
|
+
var pipelineSchema = z.object({
|
|
180
|
+
uuid: z.string().min(1),
|
|
181
|
+
build_number: z.number().int().nonnegative(),
|
|
182
|
+
state: pipelineStateSchema,
|
|
183
|
+
creator: accountRefSchema.nullable().optional(),
|
|
184
|
+
target: pipelineTargetSchema.nullable().optional(),
|
|
185
|
+
trigger: z.object({
|
|
186
|
+
name: z.string().nullable().optional(),
|
|
187
|
+
type: z.string().nullable().optional()
|
|
188
|
+
}).nullable().optional(),
|
|
189
|
+
created_on: z.iso.datetime(),
|
|
190
|
+
completed_on: z.iso.datetime().nullable().optional(),
|
|
191
|
+
duration_in_seconds: z.number().nullable().optional(),
|
|
192
|
+
build_seconds_used: z.number().nullable().optional()
|
|
193
|
+
});
|
|
194
|
+
var pipelinesResponseSchema = z.object({
|
|
195
|
+
values: z.array(pipelineSchema),
|
|
196
|
+
next: z.string().nullable().optional(),
|
|
197
|
+
page: z.number().int().nullable().optional(),
|
|
198
|
+
pagelen: z.number().int().nullable().optional()
|
|
199
|
+
});
|
|
200
|
+
var bitbucketResources = defineResources({
|
|
201
|
+
pull_request: {
|
|
202
|
+
shape: "entity",
|
|
203
|
+
description: "Open, merged, declined, and superseded pull requests with author, source/target branches, and close timestamp.",
|
|
204
|
+
endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pullrequests?state=OPEN,MERGED,DECLINED,SUPERSEDED",
|
|
205
|
+
notes: "Paginated newest-first by `updated_on`; the connector stops once a page is entirely older than `options.since`.",
|
|
206
|
+
responses: { pull_requests: pullRequestsResponseSchema }
|
|
207
|
+
},
|
|
208
|
+
pipeline: {
|
|
209
|
+
shape: "entity",
|
|
210
|
+
description: "Bitbucket Pipelines runs with state, result, target ref/commit, trigger, duration, and create/complete timestamps.",
|
|
211
|
+
endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/",
|
|
212
|
+
notes: "Paginated newest-first by `created_on`; the connector stops once a page is entirely older than `options.since`.",
|
|
213
|
+
responses: { pipelines: pipelinesResponseSchema }
|
|
214
|
+
},
|
|
215
|
+
pipeline_event: {
|
|
216
|
+
shape: "event",
|
|
217
|
+
description: "Pipeline lifecycle events. One event per pipeline covering created_on to completed_on (or updated_on if not yet finished), tagged with the terminal state and result.",
|
|
218
|
+
endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/",
|
|
219
|
+
notes: "Derived from the same pipelines response that builds the `pipeline` resource; the Bitbucket API does not expose an intermediate state-transition history endpoint."
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
var id = "bitbucket";
|
|
223
|
+
var BitbucketConnector = class _BitbucketConnector extends BaseConnector {
|
|
224
|
+
static id = id;
|
|
225
|
+
static resources = bitbucketResources;
|
|
226
|
+
static schemas = schemasFromResources(bitbucketResources);
|
|
227
|
+
static create(input, ctx) {
|
|
228
|
+
const parsed = configFields.parse(input);
|
|
229
|
+
return new _BitbucketConnector(
|
|
230
|
+
{
|
|
231
|
+
workspace: parsed.workspace,
|
|
232
|
+
repoSlugs: parsed.repoSlugs,
|
|
233
|
+
resources: parsed.resources
|
|
234
|
+
},
|
|
235
|
+
{ username: parsed.username, appPassword: parsed.appPassword },
|
|
236
|
+
ctx
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
id = id;
|
|
240
|
+
credentials = bitbucketCredentials;
|
|
241
|
+
buildHeaders() {
|
|
242
|
+
const basic = btoa(`${this.creds.username}:${this.creds.appPassword}`);
|
|
243
|
+
return {
|
|
244
|
+
Authorization: `Basic ${basic}`,
|
|
245
|
+
Accept: "application/json",
|
|
246
|
+
"User-Agent": connectorUserAgent("bitbucket")
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
fetch(url, resource, signal) {
|
|
250
|
+
return this.get(url, {
|
|
251
|
+
resource,
|
|
252
|
+
headers: this.buildHeaders(),
|
|
253
|
+
signal
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
sanitizeUrl(url, expectedPath) {
|
|
257
|
+
if (!url) {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
const u = new URL(url);
|
|
262
|
+
if (u.protocol !== "https:" || u.host !== API_HOST) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
if (u.pathname !== expectedPath) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
return u.toString();
|
|
269
|
+
} catch {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
static PHASE_RESOURCES = {
|
|
274
|
+
pull_requests: ["pull_request"],
|
|
275
|
+
pipelines: ["pipeline", "pipeline_event"]
|
|
276
|
+
};
|
|
277
|
+
activePhases(optionsResources) {
|
|
278
|
+
const fromSettings = selectActivePhases(
|
|
279
|
+
(r) => {
|
|
280
|
+
switch (r) {
|
|
281
|
+
case "pull_request":
|
|
282
|
+
return "pull_requests";
|
|
283
|
+
case "pipeline":
|
|
284
|
+
case "pipeline_event":
|
|
285
|
+
return "pipelines";
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
PHASE_ORDER,
|
|
289
|
+
this.settings.resources
|
|
290
|
+
);
|
|
291
|
+
if (optionsResources === void 0) {
|
|
292
|
+
return fromSettings;
|
|
293
|
+
}
|
|
294
|
+
return fromSettings.filter(
|
|
295
|
+
(phase) => _BitbucketConnector.PHASE_RESOURCES[phase].some(
|
|
296
|
+
(r) => optionsResources.has(r)
|
|
297
|
+
)
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
isResourceAllowed(resource, optionsResources) {
|
|
301
|
+
const fromSettings = this.settings.resources;
|
|
302
|
+
if (fromSettings && fromSettings.length > 0 && !fromSettings.includes(resource)) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
if (optionsResources !== void 0 && !optionsResources.has(resource)) {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
// -------------------------------------------------------------------------
|
|
311
|
+
// URL builders
|
|
312
|
+
// -------------------------------------------------------------------------
|
|
313
|
+
buildPullRequestsUrl(repoSlugValue, options) {
|
|
314
|
+
const u = new URL(
|
|
315
|
+
`${API_BASE}/2.0/repositories/${encodeURIComponent(this.settings.workspace)}/${encodeURIComponent(repoSlugValue)}/pullrequests`
|
|
316
|
+
);
|
|
317
|
+
u.searchParams.set("pagelen", String(PAGE_SIZE));
|
|
318
|
+
u.searchParams.set("sort", "-updated_on");
|
|
319
|
+
u.searchParams.set("state", "OPEN,MERGED,DECLINED,SUPERSEDED");
|
|
320
|
+
if (options.since) {
|
|
321
|
+
u.searchParams.set("q", `updated_on >= ${options.since}`);
|
|
322
|
+
}
|
|
323
|
+
return u.toString();
|
|
324
|
+
}
|
|
325
|
+
buildPipelinesUrl(repoSlugValue, options) {
|
|
326
|
+
const u = new URL(
|
|
327
|
+
`${API_BASE}/2.0/repositories/${encodeURIComponent(this.settings.workspace)}/${encodeURIComponent(repoSlugValue)}/pipelines/`
|
|
328
|
+
);
|
|
329
|
+
u.searchParams.set("pagelen", String(PAGE_SIZE));
|
|
330
|
+
u.searchParams.set("sort", "-created_on");
|
|
331
|
+
if (options.since) {
|
|
332
|
+
u.searchParams.set("q", `created_on >= ${options.since}`);
|
|
333
|
+
}
|
|
334
|
+
return u.toString();
|
|
335
|
+
}
|
|
336
|
+
pullRequestsPath(repoSlugValue) {
|
|
337
|
+
return `/2.0/repositories/${this.settings.workspace}/${repoSlugValue}/pullrequests`;
|
|
338
|
+
}
|
|
339
|
+
pipelinesPath(repoSlugValue) {
|
|
340
|
+
return `/2.0/repositories/${this.settings.workspace}/${repoSlugValue}/pipelines/`;
|
|
341
|
+
}
|
|
342
|
+
// -------------------------------------------------------------------------
|
|
343
|
+
// Fetchers
|
|
344
|
+
// -------------------------------------------------------------------------
|
|
345
|
+
async fetchPullRequestsPage(options, page, signal) {
|
|
346
|
+
const repos = this.settings.repoSlugs;
|
|
347
|
+
if (repos.length === 0) {
|
|
348
|
+
return { items: [], next: null };
|
|
349
|
+
}
|
|
350
|
+
const { idx, url: rawPageUrl } = decodePage(page);
|
|
351
|
+
if (idx >= repos.length) {
|
|
352
|
+
return { items: [], next: null };
|
|
353
|
+
}
|
|
354
|
+
const slug = repos[idx];
|
|
355
|
+
const expectedPath = this.pullRequestsPath(slug);
|
|
356
|
+
const fetchUrl = this.sanitizeUrl(rawPageUrl, expectedPath) ?? this.buildPullRequestsUrl(slug, options);
|
|
357
|
+
const res = await this.fetch(
|
|
358
|
+
fetchUrl,
|
|
359
|
+
"pull_requests",
|
|
360
|
+
signal
|
|
361
|
+
);
|
|
362
|
+
const rows = res.body.values;
|
|
363
|
+
const cutoff = options.since ? parseEpoch(options.since, "iso") : null;
|
|
364
|
+
let filtered;
|
|
365
|
+
let cutoffReached;
|
|
366
|
+
if (cutoff !== null) {
|
|
367
|
+
filtered = rows.filter((pr) => {
|
|
368
|
+
const ts = parseEpoch(pr.updated_on, "iso");
|
|
369
|
+
return ts === null || ts >= cutoff;
|
|
370
|
+
});
|
|
371
|
+
const last = rows.at(-1);
|
|
372
|
+
const lastTs = last ? parseEpoch(last.updated_on, "iso") : null;
|
|
373
|
+
cutoffReached = lastTs !== null && lastTs < cutoff;
|
|
374
|
+
} else {
|
|
375
|
+
filtered = rows;
|
|
376
|
+
cutoffReached = false;
|
|
377
|
+
}
|
|
378
|
+
const safeNext = this.sanitizeUrl(res.body.next ?? null, expectedPath);
|
|
379
|
+
const nextWithinRepo = cutoffReached ? null : safeNext;
|
|
380
|
+
const batch = {
|
|
381
|
+
repoSlug: slug,
|
|
382
|
+
items: filtered
|
|
383
|
+
};
|
|
384
|
+
if (nextWithinRepo !== null) {
|
|
385
|
+
return { items: [batch], next: encodePage(idx, nextWithinRepo) };
|
|
386
|
+
}
|
|
387
|
+
const nextIdx = idx + 1;
|
|
388
|
+
const next = nextIdx < repos.length ? encodePage(nextIdx, null) : null;
|
|
389
|
+
return { items: [batch], next };
|
|
390
|
+
}
|
|
391
|
+
async fetchPipelinesPage(options, page, signal) {
|
|
392
|
+
const repos = this.settings.repoSlugs;
|
|
393
|
+
if (repos.length === 0) {
|
|
394
|
+
return { items: [], next: null };
|
|
395
|
+
}
|
|
396
|
+
const { idx, url: rawPageUrl } = decodePage(page);
|
|
397
|
+
if (idx >= repos.length) {
|
|
398
|
+
return { items: [], next: null };
|
|
399
|
+
}
|
|
400
|
+
const slug = repos[idx];
|
|
401
|
+
const expectedPath = this.pipelinesPath(slug);
|
|
402
|
+
const fetchUrl = this.sanitizeUrl(rawPageUrl, expectedPath) ?? this.buildPipelinesUrl(slug, options);
|
|
403
|
+
const res = await this.fetch(
|
|
404
|
+
fetchUrl,
|
|
405
|
+
"pipelines",
|
|
406
|
+
signal
|
|
407
|
+
);
|
|
408
|
+
const rows = res.body.values;
|
|
409
|
+
const cutoff = options.since ? parseEpoch(options.since, "iso") : null;
|
|
410
|
+
let filtered;
|
|
411
|
+
let cutoffReached;
|
|
412
|
+
if (cutoff !== null) {
|
|
413
|
+
filtered = rows.filter((p) => {
|
|
414
|
+
const ts = parseEpoch(p.created_on, "iso");
|
|
415
|
+
return ts === null || ts >= cutoff;
|
|
416
|
+
});
|
|
417
|
+
const last = rows.at(-1);
|
|
418
|
+
const lastTs = last ? parseEpoch(last.created_on, "iso") : null;
|
|
419
|
+
cutoffReached = lastTs !== null && lastTs < cutoff;
|
|
420
|
+
} else {
|
|
421
|
+
filtered = rows;
|
|
422
|
+
cutoffReached = false;
|
|
423
|
+
}
|
|
424
|
+
const safeNext = this.sanitizeUrl(res.body.next ?? null, expectedPath);
|
|
425
|
+
const nextWithinRepo = cutoffReached ? null : safeNext;
|
|
426
|
+
const batch = {
|
|
427
|
+
repoSlug: slug,
|
|
428
|
+
items: filtered
|
|
429
|
+
};
|
|
430
|
+
if (nextWithinRepo !== null) {
|
|
431
|
+
return { items: [batch], next: encodePage(idx, nextWithinRepo) };
|
|
432
|
+
}
|
|
433
|
+
const nextIdx = idx + 1;
|
|
434
|
+
const next = nextIdx < repos.length ? encodePage(nextIdx, null) : null;
|
|
435
|
+
return { items: [batch], next };
|
|
436
|
+
}
|
|
437
|
+
// -------------------------------------------------------------------------
|
|
438
|
+
// Writers
|
|
439
|
+
// -------------------------------------------------------------------------
|
|
440
|
+
async writePullRequests(storage, items, page, options) {
|
|
441
|
+
if (page === null && !options.since) {
|
|
442
|
+
await storage.entities([], { types: ["pull_request"] });
|
|
443
|
+
}
|
|
444
|
+
const batches = items;
|
|
445
|
+
for (const batch of batches) {
|
|
446
|
+
for (const pr of batch.items) {
|
|
447
|
+
const createdMs = parseEpoch(pr.created_on, "iso");
|
|
448
|
+
const updatedMs = parseEpoch(pr.updated_on, "iso");
|
|
449
|
+
if (createdMs === null || updatedMs === null) {
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
await storage.entity({
|
|
453
|
+
type: "pull_request",
|
|
454
|
+
id: `${this.settings.workspace}/${batch.repoSlug}:${pr.id}`,
|
|
455
|
+
attributes: {
|
|
456
|
+
workspace: this.settings.workspace,
|
|
457
|
+
repo_slug: batch.repoSlug,
|
|
458
|
+
pull_request_id: pr.id,
|
|
459
|
+
title: pr.title,
|
|
460
|
+
state: pr.state,
|
|
461
|
+
author: pr.author?.nickname ?? pr.author?.display_name ?? null,
|
|
462
|
+
source_branch: pr.source?.branch?.name ?? null,
|
|
463
|
+
destination_branch: pr.destination?.branch?.name ?? null,
|
|
464
|
+
web_url: pr.links?.html?.href ?? null,
|
|
465
|
+
created_at: createdMs,
|
|
466
|
+
closed_at: parseEpoch(pr.closed_on ?? null, "iso")
|
|
467
|
+
},
|
|
468
|
+
updated_at: updatedMs
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
async writePipelines(storage, items, page, options) {
|
|
474
|
+
const pipelineAllowed = this.isResourceAllowed(
|
|
475
|
+
"pipeline",
|
|
476
|
+
options.resources
|
|
477
|
+
);
|
|
478
|
+
const eventAllowed = this.isResourceAllowed(
|
|
479
|
+
"pipeline_event",
|
|
480
|
+
options.resources
|
|
481
|
+
);
|
|
482
|
+
if (page === null && !options.since) {
|
|
483
|
+
if (pipelineAllowed) {
|
|
484
|
+
await storage.entities([], { types: ["pipeline"] });
|
|
485
|
+
}
|
|
486
|
+
if (eventAllowed) {
|
|
487
|
+
await storage.events([], { names: ["pipeline_event"] });
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const batches = items;
|
|
491
|
+
for (const batch of batches) {
|
|
492
|
+
for (const pipeline of batch.items) {
|
|
493
|
+
const createdMs = parseEpoch(pipeline.created_on, "iso");
|
|
494
|
+
if (createdMs === null) {
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
const completedMs = parseEpoch(pipeline.completed_on ?? null, "iso");
|
|
498
|
+
const durationMs = pipeline.duration_in_seconds !== null && pipeline.duration_in_seconds !== void 0 ? Math.round(pipeline.duration_in_seconds * 1e3) : completedMs !== null ? completedMs - createdMs : null;
|
|
499
|
+
const result = pipeline.state.result?.name ?? null;
|
|
500
|
+
const refName = pipeline.target?.ref_name ?? null;
|
|
501
|
+
const commitHash = pipeline.target?.commit?.hash ?? null;
|
|
502
|
+
const triggerType = pipeline.trigger?.type ?? null;
|
|
503
|
+
if (pipelineAllowed) {
|
|
504
|
+
await storage.entity({
|
|
505
|
+
type: "pipeline",
|
|
506
|
+
id: `${this.settings.workspace}/${batch.repoSlug}:${pipeline.uuid}`,
|
|
507
|
+
attributes: {
|
|
508
|
+
workspace: this.settings.workspace,
|
|
509
|
+
repo_slug: batch.repoSlug,
|
|
510
|
+
uuid: pipeline.uuid,
|
|
511
|
+
build_number: pipeline.build_number,
|
|
512
|
+
state: pipeline.state.name,
|
|
513
|
+
result,
|
|
514
|
+
ref_name: refName,
|
|
515
|
+
commit: commitHash,
|
|
516
|
+
trigger_type: triggerType,
|
|
517
|
+
creator: pipeline.creator?.nickname ?? pipeline.creator?.display_name ?? null,
|
|
518
|
+
created_at: createdMs,
|
|
519
|
+
completed_at: completedMs,
|
|
520
|
+
duration_ms: durationMs
|
|
521
|
+
},
|
|
522
|
+
updated_at: completedMs ?? createdMs
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
if (eventAllowed) {
|
|
526
|
+
await storage.event({
|
|
527
|
+
name: "pipeline_event",
|
|
528
|
+
start_ts: createdMs,
|
|
529
|
+
end_ts: completedMs,
|
|
530
|
+
attributes: {
|
|
531
|
+
workspace: this.settings.workspace,
|
|
532
|
+
repo_slug: batch.repoSlug,
|
|
533
|
+
uuid: pipeline.uuid,
|
|
534
|
+
build_number: pipeline.build_number,
|
|
535
|
+
state: pipeline.state.name,
|
|
536
|
+
result,
|
|
537
|
+
ref_name: refName,
|
|
538
|
+
commit: commitHash,
|
|
539
|
+
trigger_type: triggerType,
|
|
540
|
+
duration_ms: durationMs
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// -------------------------------------------------------------------------
|
|
548
|
+
// Cursor resume
|
|
549
|
+
// -------------------------------------------------------------------------
|
|
550
|
+
resolveCursor(cursor) {
|
|
551
|
+
if (!isBitbucketSyncCursor(cursor)) {
|
|
552
|
+
return void 0;
|
|
553
|
+
}
|
|
554
|
+
return { phase: cursor.phase, page: cursor.page };
|
|
555
|
+
}
|
|
556
|
+
// -------------------------------------------------------------------------
|
|
557
|
+
// sync()
|
|
558
|
+
// -------------------------------------------------------------------------
|
|
559
|
+
async sync(options, storage, signal) {
|
|
560
|
+
const cursor = this.resolveCursor(options.cursor);
|
|
561
|
+
const phases = this.activePhases(options.resources);
|
|
562
|
+
return paginateChunked({
|
|
563
|
+
phases,
|
|
564
|
+
cursor,
|
|
565
|
+
signal,
|
|
566
|
+
logger: this.logger,
|
|
567
|
+
fetchPage: async (phase, page, sig) => {
|
|
568
|
+
switch (phase) {
|
|
569
|
+
case "pull_requests":
|
|
570
|
+
return this.fetchPullRequestsPage(options, page, sig);
|
|
571
|
+
case "pipelines":
|
|
572
|
+
return this.fetchPipelinesPage(options, page, sig);
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
writeBatch: async (phase, items, page) => {
|
|
576
|
+
switch (phase) {
|
|
577
|
+
case "pull_requests":
|
|
578
|
+
return this.writePullRequests(storage, items, page, options);
|
|
579
|
+
case "pipelines":
|
|
580
|
+
return this.writePipelines(storage, items, page, options);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
// src/index.ts
|
|
588
|
+
var index_default = BitbucketConnector;
|
|
589
|
+
export {
|
|
590
|
+
BitbucketConnector,
|
|
591
|
+
configFields,
|
|
592
|
+
index_default as default,
|
|
593
|
+
doc,
|
|
594
|
+
id,
|
|
595
|
+
bitbucketResources as resources
|
|
596
|
+
};
|
|
597
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../connector-shared/src/errors.ts","../../../connector-shared/src/retry.ts","../../../connector-shared/src/version.ts","../../../connector-shared/src/request.ts","../../../connector-shared/src/rate-limit.ts","../../../connector-shared/src/map-concurrent.ts","../../../connector-shared/src/sanitize.ts","../../../connector-shared/src/epoch.ts","../../../connector-shared/src/pagination.ts","../../../connector-shared/src/logger.ts","../src/bitbucket.ts","../src/index.ts"],"sourcesContent":["import type { HttpResponse } from './types';\n\nexport type HttpErrorKind =\n | 'transient'\n | 'rate_limit'\n | 'auth'\n | 'upstream_bug'\n | 'client_bug';\n\nexport abstract class HttpClientError extends Error {\n abstract readonly kind: HttpErrorKind;\n readonly response?: HttpResponse;\n\n constructor(message: string, response?: HttpResponse) {\n super(message);\n this.name = new.target.name;\n this.response = response;\n }\n}\n\nexport class TransientError extends HttpClientError {\n readonly kind = 'transient' as const;\n}\n\nexport class RateLimitError extends HttpClientError {\n readonly kind = 'rate_limit' as const;\n readonly retryAfter?: Date;\n\n constructor(message: string, response?: HttpResponse, retryAfter?: Date) {\n super(message, response);\n this.retryAfter = retryAfter;\n }\n}\n\nexport class AuthError extends HttpClientError {\n readonly kind = 'auth' as const;\n}\n\nexport class UpstreamBugError extends HttpClientError {\n readonly kind = 'upstream_bug' as const;\n}\n\nexport class ClientBugError extends HttpClientError {\n readonly kind = 'client_bug' as const;\n}\n\nexport function classifyStatus(status: number): HttpErrorKind {\n if (status === 429) {\n return 'rate_limit';\n }\n if (status === 401 || status === 403) {\n return 'auth';\n }\n if (status === 408) {\n return 'transient';\n }\n if (status >= 500) {\n return 'upstream_bug';\n }\n if (status >= 400) {\n return 'client_bug';\n }\n return 'client_bug';\n}\n\nexport function errorForStatus(\n message: string,\n response: HttpResponse,\n retryAfter?: Date,\n): HttpClientError {\n const kind = classifyStatus(response.status);\n switch (kind) {\n case 'rate_limit':\n return new RateLimitError(message, response, retryAfter);\n case 'auth':\n return new AuthError(message, response);\n case 'transient':\n return new TransientError(message, response);\n case 'upstream_bug':\n return new UpstreamBugError(message, response);\n case 'client_bug':\n return new ClientBugError(message, response);\n }\n}\n","import { HttpClientError, RateLimitError, TransientError } from './errors';\n\nexport interface RetryPolicy {\n maxAttempts?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n retryOn?: (status: number | null, err?: Error) => boolean;\n}\n\nexport const defaultRetryOn = (status: number | null, err?: Error): boolean => {\n if (err instanceof RateLimitError) {\n return true;\n }\n if (err instanceof TransientError) {\n return true;\n }\n if (status === null) {\n return err instanceof Error && !(err instanceof HttpClientError);\n }\n if (status === 408 || status === 429) {\n return true;\n }\n if (status >= 500) {\n return true;\n }\n return false;\n};\n\nexport function backoffDelayMs(\n attempt: number,\n policy: Required<Pick<RetryPolicy, 'initialDelayMs' | 'maxDelayMs'>>,\n): number {\n const base = policy.initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, policy.maxDelayMs);\n}\n\nexport function parseRetryAfter(\n headerValue: string | null,\n now: Date = new Date(),\n): Date | undefined {\n if (!headerValue) {\n return undefined;\n }\n const trimmed = headerValue.trim();\n if (/^\\d+$/.test(trimmed)) {\n return new Date(now.getTime() + Number(trimmed) * 1000);\n }\n const parsed = Date.parse(trimmed);\n if (Number.isNaN(parsed)) {\n return undefined;\n }\n return new Date(parsed);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.reject(signal.reason ?? new Error('Aborted'));\n }\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason ?? new Error('Aborted'));\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n","export const HTTP_CLIENT_VERSION = '0.0.0';\n\nexport const DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n\nexport function connectorUserAgent(connectorId: string): string {\n return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n}\n","import {\n AuthError,\n ClientBugError,\n HttpClientError,\n RateLimitError,\n TransientError,\n UpstreamBugError,\n errorForStatus,\n} from './errors';\nimport { defaultRetryOn, parseRetryAfter, sleep } from './retry';\nimport type { FetchLike, HttpMethod, HttpRequest, HttpResponse } from './types';\nimport { DEFAULT_USER_AGENT } from './version';\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_ATTEMPTS = 3;\nconst DEFAULT_INITIAL_DELAY_MS = 1000;\nconst DEFAULT_MAX_DELAY_MS = 60_000;\nconst OBSERVER_TIMEOUT_MS = 250;\n\nexport interface RequestObservation {\n url: string;\n method: HttpMethod;\n status: number;\n resource: string;\n requestId: string;\n body: unknown;\n}\n\nexport type RequestObserver = (\n event: RequestObservation,\n) => void | Promise<void>;\n\nexport interface RequestOptions {\n fetch?: FetchLike;\n observer?: RequestObserver;\n resource: string;\n requestId?: string;\n}\n\nasync function notifyObserver(\n observer: RequestObserver,\n event: RequestObservation,\n): Promise<void> {\n let result: void | Promise<void>;\n try {\n result = observer(event);\n } catch (err) {\n console.warn('[connector-shared] request observer threw:', err);\n return;\n }\n if (!(result instanceof Promise)) {\n return;\n }\n const guarded = result.catch((err) => {\n console.warn('[connector-shared] request observer rejected:', err);\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, OBSERVER_TIMEOUT_MS);\n });\n try {\n await Promise.race([guarded, timeout]);\n } finally {\n if (timer) {\n clearTimeout(timer);\n }\n }\n}\n\nfunction newRequestId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) {\n return c.randomUUID();\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction mergeHeaders(\n defaults: Record<string, string>,\n overrides: Record<string, string> | undefined,\n): Record<string, string> {\n const merged: Record<string, string> = {};\n for (const [k, v] of Object.entries(defaults)) {\n merged[k.toLowerCase()] = v;\n }\n if (overrides) {\n for (const [k, v] of Object.entries(overrides)) {\n merged[k.toLowerCase()] = v;\n }\n }\n return merged;\n}\n\nfunction linkTimeoutSignal(\n parent: AbortSignal | undefined,\n timeoutMs: number,\n): { signal: AbortSignal; cancel: () => void } {\n const controller = new AbortController();\n const onParentAbort = () => {\n controller.abort(parent?.reason);\n };\n if (parent) {\n if (parent.aborted) {\n controller.abort(parent.reason);\n } else {\n parent.addEventListener('abort', onParentAbort, { once: true });\n }\n }\n const timer = setTimeout(() => {\n controller.abort(new Error(`Request timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n return {\n signal: controller.signal,\n cancel: () => {\n clearTimeout(timer);\n if (parent) {\n parent.removeEventListener('abort', onParentAbort);\n }\n },\n };\n}\n\nasync function readBody(res: Response, parseJson: boolean): Promise<unknown> {\n if (res.status === 204 || res.status === 205) {\n return null;\n }\n const contentType = res.headers.get('content-type') ?? '';\n if (parseJson && contentType.includes('application/json')) {\n const text = await res.text();\n if (text.length === 0) {\n return null;\n }\n return JSON.parse(text);\n }\n return res.text();\n}\n\nexport async function request<T = unknown>(\n req: HttpRequest,\n options: RequestOptions,\n): Promise<HttpResponse<T>> {\n const fetchImpl: FetchLike = options.fetch ?? (globalThis.fetch as FetchLike);\n const retry = req.retry ?? {};\n const maxAttempts = retry.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n const initialDelayMs = retry.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;\n const maxDelayMs = retry.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;\n const retryOn = retry.retryOn ?? defaultRetryOn;\n const timeoutMs = req.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const parseJson = req.parseJson ?? true;\n\n const headers = mergeHeaders(\n {\n 'User-Agent': DEFAULT_USER_AGENT,\n Accept: 'application/json',\n },\n req.headers,\n );\n\n let lastErr: Error | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n req.signal?.throwIfAborted();\n\n const { signal, cancel } = linkTimeoutSignal(req.signal, timeoutMs);\n let res: Response;\n try {\n res = await fetchImpl(req.url, {\n method: req.method ?? 'GET',\n headers,\n body: req.body as RequestInit['body'],\n signal,\n });\n } catch (err) {\n cancel();\n if (req.signal?.aborted) {\n throw req.signal.reason ?? err;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n lastErr = error;\n if (attempt < maxAttempts - 1 && retryOn(null, error)) {\n const delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n await sleep(delay, req.signal);\n continue;\n }\n throw new TransientError(error.message);\n }\n cancel();\n\n const body = await readBody(res, parseJson);\n const httpResponse: HttpResponse<T> = {\n status: res.status,\n headers: res.headers,\n body: body as T,\n };\n if (req.rateLimit) {\n const state = req.rateLimit.parse(res.headers);\n if (state) {\n httpResponse.rateLimitState = state;\n }\n }\n\n if (options.observer) {\n await notifyObserver(options.observer, {\n url: req.url,\n method: req.method ?? 'GET',\n status: res.status,\n resource: options.resource,\n requestId: options.requestId ?? newRequestId(),\n body,\n });\n }\n\n if (res.ok) {\n return httpResponse;\n }\n\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\n const message = `HTTP ${res.status} ${res.statusText} for ${req.method ?? 'GET'} ${req.url}`;\n const err = errorForStatus(message, httpResponse, retryAfter);\n\n if (\n attempt < maxAttempts - 1 &&\n retryOn(res.status, err) &&\n !(err instanceof AuthError) &&\n !(err instanceof ClientBugError)\n ) {\n lastErr = err;\n let delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n if (err instanceof RateLimitError && retryAfter) {\n const wait = retryAfter.getTime() - Date.now();\n if (wait > 0) {\n delay = Math.min(wait, maxDelayMs);\n }\n }\n await sleep(delay, req.signal);\n continue;\n }\n\n throw err;\n }\n\n throw lastErr ?? new UpstreamBugError('Exhausted retry attempts');\n}\n\nfunction computeDelay(\n attempt: number,\n initialDelayMs: number,\n maxDelayMs: number,\n): number {\n const base = initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, maxDelayMs);\n}\n\nexport { HttpClientError };\n","export interface RateLimitState {\n remaining: number;\n resetAt: Date;\n}\n\nexport interface RateLimitPolicy {\n parse(headers: Headers): RateLimitState | null;\n}\n\nexport interface StandardRateLimitPolicyConfig {\n remainingHeader: string;\n resetHeader: string;\n resetUnit: 's' | 'ms';\n resetFallbackMs?: number;\n}\n\nexport function standardRateLimitPolicy(\n config: StandardRateLimitPolicyConfig,\n): RateLimitPolicy {\n const { remainingHeader, resetHeader, resetUnit, resetFallbackMs } = config;\n const multiplier = resetUnit === 's' ? 1000 : 1;\n return {\n parse(h) {\n const remainingRaw = h.get(remainingHeader);\n if (remainingRaw === null || remainingRaw.trim() === '') {\n return null;\n }\n const remaining = Number(remainingRaw);\n if (!Number.isFinite(remaining)) {\n return null;\n }\n const resetRaw = h.get(resetHeader);\n if (resetRaw === null) {\n if (resetFallbackMs === undefined) {\n return null;\n }\n return {\n remaining,\n resetAt: new Date(Date.now() + resetFallbackMs),\n };\n }\n if (resetRaw.trim() === '') {\n return null;\n }\n const reset = Number(resetRaw);\n if (!Number.isFinite(reset) || reset < 0) {\n return null;\n }\n const resetMs = reset * multiplier;\n if (!Number.isFinite(resetMs)) {\n return null;\n }\n return { remaining, resetAt: new Date(resetMs) };\n },\n };\n}\n","export async function mapWithConcurrency<T, R>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n if (items.length === 0) {\n return results;\n }\n const normalized = Number.isFinite(concurrency) ? Math.floor(concurrency) : 1;\n const limit = Math.max(1, Math.min(normalized, items.length));\n let next = 0;\n let failed = false;\n\n async function worker(): Promise<void> {\n while (!failed) {\n const i = next++;\n if (i >= items.length) {\n return;\n }\n try {\n results[i] = await fn(items[i]!, i);\n } catch (err) {\n failed = true;\n throw err;\n }\n }\n }\n\n const workers: Promise<void>[] = [];\n for (let w = 0; w < limit; w++) {\n workers.push(worker());\n }\n await Promise.all(workers);\n return results;\n}\n","export interface SanitizeAllowedUrlOptions {\n url: string | null;\n host: string;\n pathname: string;\n protocol?: 'https:' | 'http:';\n}\n\nexport function sanitizeAllowedUrl(\n options: SanitizeAllowedUrlOptions,\n): string | null {\n const { url, host, pathname, protocol = 'https:' } = options;\n if (url === null) {\n return null;\n }\n try {\n const u = new URL(url);\n if (u.protocol !== protocol || u.host !== host || u.pathname !== pathname) {\n return null;\n }\n return u.toString();\n } catch {\n return null;\n }\n}\n","export type EpochUnit = 'ms' | 's' | 'iso';\n\nexport function parseEpoch(\n value: number | string | null | undefined,\n unit: EpochUnit,\n): number | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (unit === 'iso') {\n if (typeof value !== 'string') {\n return null;\n }\n const ms = new Date(value).getTime();\n return Number.isFinite(ms) ? ms : null;\n }\n if (typeof value === 'string' && value.trim() === '') {\n return null;\n }\n const n = typeof value === 'number' ? value : Number(value);\n if (!Number.isFinite(n)) {\n return null;\n }\n const result = unit === 's' ? n * 1000 : n;\n return Number.isFinite(result) ? result : null;\n}\n","import { request } from './request';\nimport type { HttpRequest } from './types';\n\nexport function parseLinkHeader(header: string | null): Record<string, string> {\n if (!header) {\n return {};\n }\n const result: Record<string, string> = {};\n for (const part of header.split(',')) {\n const match = part.match(/<([^>]+)>\\s*;\\s*rel=\"([^\"]+)\"/);\n if (match) {\n result[match[2]!] = match[1]!;\n }\n }\n return result;\n}\n\nexport async function* paginateLink<T>(\n initial: HttpRequest,\n parse: (body: unknown) => T[],\n options: { resource: string },\n): AsyncIterable<T> {\n let next: string | null = initial.url;\n while (next) {\n const res: Awaited<ReturnType<typeof request>> = await request(\n {\n ...initial,\n url: next,\n },\n { resource: options.resource },\n );\n for (const item of parse(res.body)) {\n yield item;\n }\n const links = parseLinkHeader(res.headers.get('link'));\n next = links['next'] ?? null;\n }\n}\n\nexport async function* paginateCursor<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; nextCursor: string | null },\n buildNext: (req: HttpRequest, cursor: string) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let req: HttpRequest = initial;\n while (true) {\n const res = await request(req, { resource: options.resource });\n const { items, nextCursor } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!nextCursor) {\n return;\n }\n req = buildNext(req, nextCursor);\n }\n}\n\nexport async function* paginatePage<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; hasMore: boolean },\n buildPage: (req: HttpRequest, page: number) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let page = 1;\n while (true) {\n const req = page === 1 ? initial : buildPage(initial, page);\n const res = await request(req, { resource: options.resource });\n const { items, hasMore } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!hasMore || items.length === 0) {\n return;\n }\n page++;\n }\n}\n","export type LogFields = Record<string, unknown>;\n\nexport interface ConnectorLogger {\n info(event: string, fields?: LogFields): void;\n warn(event: string, fields?: LogFields): void;\n}\n\nexport interface ConnectorLoggerOptions {\n scope: string;\n}\n\nconst MAX_VALUE_LEN = 120;\n\nfunction truncate(s: string, max = MAX_VALUE_LEN): string {\n if (s.length <= max) {\n return s;\n }\n return `${s.slice(0, max - 1)}…`;\n}\n\nfunction formatValue(value: unknown): string {\n if (value === null) {\n return 'null';\n }\n if (value === undefined) {\n return '';\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n if (typeof value === 'string') {\n const t = truncate(value);\n if (/[\\s\"=]/.test(t)) {\n return JSON.stringify(t);\n }\n return t;\n }\n if (typeof value === 'bigint') {\n return value.toString();\n }\n let json: string | undefined;\n try {\n json = JSON.stringify(value);\n } catch {\n json = undefined;\n }\n return truncate(json ?? String(value));\n}\n\nexport function formatLogFields(fields?: LogFields): string {\n if (!fields) {\n return '';\n }\n const parts: string[] = [];\n for (const [k, v] of Object.entries(fields)) {\n if (v === undefined) {\n continue;\n }\n parts.push(`${k}=${formatValue(v)}`);\n }\n return parts.length > 0 ? ` ${parts.join(' ')}` : '';\n}\n\nexport function formatLogLine(\n scope: string,\n event: string,\n fields?: LogFields,\n): string {\n return `[${scope}] ${event}${formatLogFields(fields)}`;\n}\n\nexport function createDefaultConnectorLogger(\n opts: ConnectorLoggerOptions,\n): ConnectorLogger {\n return {\n info(event, fields) {\n console.info(formatLogLine(opts.scope, event, fields));\n },\n warn(event, fields) {\n console.warn(formatLogLine(opts.scope, event, fields));\n },\n };\n}\n\nconst NOOP_LOGGER: ConnectorLogger = {\n info() {},\n warn() {},\n};\n\nexport function noopConnectorLogger(): ConnectorLogger {\n return NOOP_LOGGER;\n}\n","import {\n type HttpResponse,\n connectorUserAgent,\n parseEpoch,\n} from '@rawdash/connector-shared';\nimport {\n BaseConnector,\n type ChunkedSyncCursor,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type FetchPageResult,\n type StorageHandle,\n type SyncOptions,\n type SyncResult,\n defineConfigFields,\n defineConnectorDoc,\n defineResources,\n makeChunkedCursorGuard,\n paginateChunked,\n schemasFromResources,\n selectActivePhases,\n} from '@rawdash/core';\nimport { z } from 'zod';\n\n// ---------------------------------------------------------------------------\n// configFields\n// ---------------------------------------------------------------------------\n\nconst repoSlug = z\n .string()\n .min(1)\n .regex(\n /^[^/\\s?#]+$/,\n 'Use the repository slug only (no workspace prefix, slashes, or query).',\n );\n\nexport const configFields = defineConfigFields(\n z.object({\n workspace: z\n .string()\n .min(1)\n .regex(\n /^[^/\\s?#]+$/,\n 'Use the workspace slug only (no slashes, spaces, or query).',\n )\n .meta({\n label: 'Workspace',\n description:\n 'Bitbucket Cloud workspace slug (the segment shown in repo URLs after bitbucket.org/).',\n placeholder: 'my-workspace',\n }),\n username: z.string().min(1).meta({\n label: 'Atlassian username',\n description:\n 'Atlassian account username paired with the app password for Basic auth (find it under Personal settings -> Account settings).',\n placeholder: 'janedoe',\n }),\n appPassword: z.object({ $secret: z.string() }).meta({\n label: 'App password',\n description:\n 'Bitbucket app password with `Repositories:Read` and `Pipelines:Read` scopes. Create one at Personal settings -> App passwords.',\n placeholder: 'ATBB...',\n secret: true,\n }),\n repoSlugs: z\n .array(repoSlug)\n .nonempty()\n .refine((slugs) => new Set(slugs).size === slugs.length, {\n error: 'Repository slugs must be unique.',\n })\n .meta({\n label: 'Repository slugs',\n description:\n 'Repositories to sync, named by their slug within the workspace (no `workspace/` prefix).',\n }),\n resources: z\n .array(z.enum(['pull_request', 'pipeline', 'pipeline_event']))\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n \"Which Bitbucket resources to sync. Omit to sync all of them. 'pipeline_event' rides the 'pipeline' phase - enabling it without 'pipeline' still fetches pipelines but skips writing pipeline entities.\",\n }),\n }),\n);\n\n// ---------------------------------------------------------------------------\n// Connector documentation metadata\n// ---------------------------------------------------------------------------\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'Bitbucket',\n category: 'engineering',\n brandColor: '#0052CC',\n tagline:\n 'Sync pull requests, pipelines, and pipeline lifecycle events from Bitbucket Cloud repositories.',\n vendor: {\n name: 'Atlassian',\n apiDocs: 'https://developer.atlassian.com/cloud/bitbucket/rest/intro/',\n website: 'https://bitbucket.org',\n },\n auth: {\n summary:\n 'Authenticates over HTTP Basic auth using an Atlassian account username and a Bitbucket app password. The password is scoped to the projects and repositories the account can already read.',\n setup: [\n 'Open Bitbucket -> Personal settings -> App passwords (https://bitbucket.org/account/settings/app-passwords/).',\n 'Create an app password with `Repositories:Read` and `Pipelines:Read` scopes.',\n 'Store it as a secret and reference it from the connector config as `appPassword: secret(\"BITBUCKET_APP_PASSWORD\")`, alongside your `workspace`, `username`, and the list of `repoSlugs` to sync.',\n ],\n },\n rateLimit:\n 'Bitbucket Cloud applies hourly per-IP and per-user limits (around 1,000 requests/hour for app-password auth). Pagination uses a `next` URL in each response and a configurable `pagelen` (capped at 50 here).',\n limitations: [\n 'Bitbucket Server / Data Center are out of scope; this connector targets Bitbucket Cloud only.',\n 'Pipeline state-transition events are synthesized: one `pipeline_event` is emitted per pipeline lifecycle (created_on to completed_on/updated_on), not one per intermediate state change.',\n 'Repository discovery is not automatic - configure each repository slug explicitly via `repoSlugs`.',\n ],\n});\n\n// ---------------------------------------------------------------------------\n// Settings and credentials\n// ---------------------------------------------------------------------------\n\nexport type BitbucketResource = 'pull_request' | 'pipeline' | 'pipeline_event';\n\nexport interface BitbucketSettings {\n workspace: string;\n repoSlugs: readonly string[];\n resources?: readonly BitbucketResource[];\n}\n\nconst bitbucketCredentials = {\n username: {\n description: 'Atlassian account username',\n auth: 'required' as const,\n },\n appPassword: {\n description: 'Bitbucket app password',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype BitbucketCredentials = typeof bitbucketCredentials;\n\nconst API_HOST = 'api.bitbucket.org';\nconst API_BASE = `https://${API_HOST}`;\nconst PAGE_SIZE = 50;\n\n// ---------------------------------------------------------------------------\n// Sync phases + cursor\n// ---------------------------------------------------------------------------\n\nconst PHASE_ORDER = ['pull_requests', 'pipelines'] as const;\n\ntype BitbucketPhase = (typeof PHASE_ORDER)[number];\n\ntype BitbucketSyncCursor = ChunkedSyncCursor<BitbucketPhase, string>;\n\nconst isBitbucketSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\n// Page-cursor encoding: `<repoIdx>|<pageUrl?>`.\n// - null page -> start at repoIdx=0 with no URL yet\n// - \"<idx>|\" -> start of repo at idx, build initial URL\n// - \"<idx>|<url>\" -> continuing pagination for repo at idx\nfunction decodePage(page: string | null): {\n idx: number;\n url: string | null;\n} {\n if (page === null) {\n return { idx: 0, url: null };\n }\n const sep = page.indexOf('|');\n if (sep === -1) {\n return { idx: 0, url: null };\n }\n const idxRaw = Number.parseInt(page.slice(0, sep), 10);\n const url = page.slice(sep + 1);\n return {\n idx: Number.isFinite(idxRaw) && idxRaw >= 0 ? idxRaw : 0,\n url: url === '' ? null : url,\n };\n}\n\nfunction encodePage(idx: number, url: string | null): string {\n return `${idx}|${url ?? ''}`;\n}\n\n// ---------------------------------------------------------------------------\n// Bitbucket API types\n// ---------------------------------------------------------------------------\n\ninterface BitbucketAccountRef {\n uuid?: string | null;\n display_name?: string | null;\n nickname?: string | null;\n account_id?: string | null;\n}\n\ninterface BitbucketBranchRef {\n branch?: { name?: string | null } | null;\n commit?: { hash?: string | null } | null;\n}\n\ninterface BitbucketPullRequest {\n id: number;\n title: string;\n state: string;\n author?: BitbucketAccountRef | null;\n source?: BitbucketBranchRef | null;\n destination?: BitbucketBranchRef | null;\n created_on: string;\n updated_on: string;\n closed_on?: string | null;\n links?: { html?: { href?: string | null } | null } | null;\n}\n\ninterface BitbucketPullRequestsResponse {\n values: BitbucketPullRequest[];\n next?: string | null;\n page?: number | null;\n pagelen?: number | null;\n}\n\ninterface BitbucketPipelineTarget {\n ref_name?: string | null;\n commit?: { hash?: string | null } | null;\n selector?: { type?: string | null; pattern?: string | null } | null;\n}\n\ninterface BitbucketPipelineState {\n name: string;\n type?: string | null;\n result?: { name?: string | null } | null;\n}\n\ninterface BitbucketPipeline {\n uuid: string;\n build_number: number;\n state: BitbucketPipelineState;\n creator?: BitbucketAccountRef | null;\n target?: BitbucketPipelineTarget | null;\n trigger?: { name?: string | null; type?: string | null } | null;\n created_on: string;\n completed_on?: string | null;\n duration_in_seconds?: number | null;\n build_seconds_used?: number | null;\n}\n\ninterface BitbucketPipelinesResponse {\n values: BitbucketPipeline[];\n next?: string | null;\n page?: number | null;\n pagelen?: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// Zod response schemas\n// ---------------------------------------------------------------------------\n\nconst accountRefSchema = z.object({\n uuid: z.string().nullable().optional(),\n display_name: z.string().nullable().optional(),\n nickname: z.string().nullable().optional(),\n account_id: z.string().nullable().optional(),\n});\n\nconst branchRefSchema = z.object({\n branch: z\n .object({ name: z.string().nullable().optional() })\n .nullable()\n .optional(),\n commit: z\n .object({ hash: z.string().nullable().optional() })\n .nullable()\n .optional(),\n});\n\nconst pullRequestSchema = z.object({\n id: z.number().int().nonnegative(),\n title: z.string(),\n state: z.string().min(1),\n author: accountRefSchema.nullable().optional(),\n source: branchRefSchema.nullable().optional(),\n destination: branchRefSchema.nullable().optional(),\n created_on: z.iso.datetime(),\n updated_on: z.iso.datetime(),\n closed_on: z.iso.datetime().nullable().optional(),\n links: z\n .object({\n html: z\n .object({ href: z.string().nullable().optional() })\n .nullable()\n .optional(),\n })\n .nullable()\n .optional(),\n});\n\nconst pullRequestsResponseSchema = z.object({\n values: z.array(pullRequestSchema),\n next: z.string().nullable().optional(),\n page: z.number().int().nullable().optional(),\n pagelen: z.number().int().nullable().optional(),\n});\n\nconst pipelineStateSchema = z.object({\n name: z.string().min(1),\n type: z.string().nullable().optional(),\n result: z\n .object({ name: z.string().nullable().optional() })\n .nullable()\n .optional(),\n});\n\nconst pipelineTargetSchema = z.object({\n ref_name: z.string().nullable().optional(),\n commit: z\n .object({ hash: z.string().nullable().optional() })\n .nullable()\n .optional(),\n selector: z\n .object({\n type: z.string().nullable().optional(),\n pattern: z.string().nullable().optional(),\n })\n .nullable()\n .optional(),\n});\n\nconst pipelineSchema = z.object({\n uuid: z.string().min(1),\n build_number: z.number().int().nonnegative(),\n state: pipelineStateSchema,\n creator: accountRefSchema.nullable().optional(),\n target: pipelineTargetSchema.nullable().optional(),\n trigger: z\n .object({\n name: z.string().nullable().optional(),\n type: z.string().nullable().optional(),\n })\n .nullable()\n .optional(),\n created_on: z.iso.datetime(),\n completed_on: z.iso.datetime().nullable().optional(),\n duration_in_seconds: z.number().nullable().optional(),\n build_seconds_used: z.number().nullable().optional(),\n});\n\nconst pipelinesResponseSchema = z.object({\n values: z.array(pipelineSchema),\n next: z.string().nullable().optional(),\n page: z.number().int().nullable().optional(),\n pagelen: z.number().int().nullable().optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Resource definitions\n// ---------------------------------------------------------------------------\n\nexport const bitbucketResources = defineResources({\n pull_request: {\n shape: 'entity',\n description:\n 'Open, merged, declined, and superseded pull requests with author, source/target branches, and close timestamp.',\n endpoint:\n 'GET /2.0/repositories/{workspace}/{repo_slug}/pullrequests?state=OPEN,MERGED,DECLINED,SUPERSEDED',\n notes:\n 'Paginated newest-first by `updated_on`; the connector stops once a page is entirely older than `options.since`.',\n responses: { pull_requests: pullRequestsResponseSchema },\n },\n pipeline: {\n shape: 'entity',\n description:\n 'Bitbucket Pipelines runs with state, result, target ref/commit, trigger, duration, and create/complete timestamps.',\n endpoint: 'GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/',\n notes:\n 'Paginated newest-first by `created_on`; the connector stops once a page is entirely older than `options.since`.',\n responses: { pipelines: pipelinesResponseSchema },\n },\n pipeline_event: {\n shape: 'event',\n description:\n 'Pipeline lifecycle events. One event per pipeline covering created_on to completed_on (or updated_on if not yet finished), tagged with the terminal state and result.',\n endpoint: 'GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/',\n notes:\n 'Derived from the same pipelines response that builds the `pipeline` resource; the Bitbucket API does not expose an intermediate state-transition history endpoint.',\n },\n});\n\nexport const id = 'bitbucket';\n\n// ---------------------------------------------------------------------------\n// Connector class\n// ---------------------------------------------------------------------------\n\ninterface RepoBatch<T> {\n repoSlug: string;\n items: T[];\n}\n\nexport class BitbucketConnector extends BaseConnector<\n BitbucketSettings,\n BitbucketCredentials\n> {\n static readonly id = id;\n\n static readonly resources = bitbucketResources;\n\n static readonly schemas = schemasFromResources(bitbucketResources);\n\n static create(input: unknown, ctx?: ConnectorContext): BitbucketConnector {\n const parsed = configFields.parse(input);\n return new BitbucketConnector(\n {\n workspace: parsed.workspace,\n repoSlugs: parsed.repoSlugs,\n resources: parsed.resources,\n },\n { username: parsed.username, appPassword: parsed.appPassword },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = bitbucketCredentials;\n\n private buildHeaders(): Record<string, string> {\n const basic = btoa(`${this.creds.username}:${this.creds.appPassword}`);\n return {\n Authorization: `Basic ${basic}`,\n Accept: 'application/json',\n 'User-Agent': connectorUserAgent('bitbucket'),\n };\n }\n\n private fetch<T>(\n url: string,\n resource: string,\n signal: AbortSignal | undefined,\n ): Promise<HttpResponse<T>> {\n return this.get<T>(url, {\n resource,\n headers: this.buildHeaders(),\n signal,\n });\n }\n\n private sanitizeUrl(url: string | null, expectedPath: string): string | null {\n if (!url) {\n return null;\n }\n try {\n const u = new URL(url);\n if (u.protocol !== 'https:' || u.host !== API_HOST) {\n return null;\n }\n if (u.pathname !== expectedPath) {\n return null;\n }\n return u.toString();\n } catch {\n return null;\n }\n }\n\n private static readonly PHASE_RESOURCES: Record<\n BitbucketPhase,\n readonly BitbucketResource[]\n > = {\n pull_requests: ['pull_request'],\n pipelines: ['pipeline', 'pipeline_event'],\n };\n\n private activePhases(\n optionsResources: ReadonlySet<string> | undefined,\n ): BitbucketPhase[] {\n const fromSettings = selectActivePhases<BitbucketResource, BitbucketPhase>(\n (r) => {\n switch (r) {\n case 'pull_request':\n return 'pull_requests';\n case 'pipeline':\n case 'pipeline_event':\n return 'pipelines';\n }\n },\n PHASE_ORDER,\n this.settings.resources,\n );\n if (optionsResources === undefined) {\n return fromSettings;\n }\n return fromSettings.filter((phase) =>\n BitbucketConnector.PHASE_RESOURCES[phase].some((r) =>\n optionsResources.has(r),\n ),\n );\n }\n\n private isResourceAllowed(\n resource: BitbucketResource,\n optionsResources: ReadonlySet<string> | undefined,\n ): boolean {\n const fromSettings = this.settings.resources;\n if (\n fromSettings &&\n fromSettings.length > 0 &&\n !fromSettings.includes(resource)\n ) {\n return false;\n }\n if (optionsResources !== undefined && !optionsResources.has(resource)) {\n return false;\n }\n return true;\n }\n\n // -------------------------------------------------------------------------\n // URL builders\n // -------------------------------------------------------------------------\n\n private buildPullRequestsUrl(\n repoSlugValue: string,\n options: SyncOptions,\n ): string {\n const u = new URL(\n `${API_BASE}/2.0/repositories/${encodeURIComponent(this.settings.workspace)}/${encodeURIComponent(repoSlugValue)}/pullrequests`,\n );\n u.searchParams.set('pagelen', String(PAGE_SIZE));\n u.searchParams.set('sort', '-updated_on');\n u.searchParams.set('state', 'OPEN,MERGED,DECLINED,SUPERSEDED');\n if (options.since) {\n u.searchParams.set('q', `updated_on >= ${options.since}`);\n }\n return u.toString();\n }\n\n private buildPipelinesUrl(\n repoSlugValue: string,\n options: SyncOptions,\n ): string {\n const u = new URL(\n `${API_BASE}/2.0/repositories/${encodeURIComponent(this.settings.workspace)}/${encodeURIComponent(repoSlugValue)}/pipelines/`,\n );\n u.searchParams.set('pagelen', String(PAGE_SIZE));\n u.searchParams.set('sort', '-created_on');\n if (options.since) {\n u.searchParams.set('q', `created_on >= ${options.since}`);\n }\n return u.toString();\n }\n\n private pullRequestsPath(repoSlugValue: string): string {\n return `/2.0/repositories/${this.settings.workspace}/${repoSlugValue}/pullrequests`;\n }\n\n private pipelinesPath(repoSlugValue: string): string {\n return `/2.0/repositories/${this.settings.workspace}/${repoSlugValue}/pipelines/`;\n }\n\n // -------------------------------------------------------------------------\n // Fetchers\n // -------------------------------------------------------------------------\n\n private async fetchPullRequestsPage(\n options: SyncOptions,\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<FetchPageResult<string>> {\n const repos = this.settings.repoSlugs;\n if (repos.length === 0) {\n return { items: [], next: null };\n }\n const { idx, url: rawPageUrl } = decodePage(page);\n if (idx >= repos.length) {\n return { items: [], next: null };\n }\n const slug = repos[idx]!;\n const expectedPath = this.pullRequestsPath(slug);\n const fetchUrl =\n this.sanitizeUrl(rawPageUrl, expectedPath) ??\n this.buildPullRequestsUrl(slug, options);\n const res = await this.fetch<BitbucketPullRequestsResponse>(\n fetchUrl,\n 'pull_requests',\n signal,\n );\n const rows = res.body.values;\n const cutoff = options.since ? parseEpoch(options.since, 'iso') : null;\n let filtered: BitbucketPullRequest[];\n let cutoffReached: boolean;\n if (cutoff !== null) {\n filtered = rows.filter((pr) => {\n const ts = parseEpoch(pr.updated_on, 'iso');\n return ts === null || ts >= cutoff;\n });\n const last = rows.at(-1);\n const lastTs = last ? parseEpoch(last.updated_on, 'iso') : null;\n cutoffReached = lastTs !== null && lastTs < cutoff;\n } else {\n filtered = rows;\n cutoffReached = false;\n }\n const safeNext = this.sanitizeUrl(res.body.next ?? null, expectedPath);\n const nextWithinRepo = cutoffReached ? null : safeNext;\n const batch: RepoBatch<BitbucketPullRequest> = {\n repoSlug: slug,\n items: filtered,\n };\n if (nextWithinRepo !== null) {\n return { items: [batch], next: encodePage(idx, nextWithinRepo) };\n }\n const nextIdx = idx + 1;\n const next = nextIdx < repos.length ? encodePage(nextIdx, null) : null;\n return { items: [batch], next };\n }\n\n private async fetchPipelinesPage(\n options: SyncOptions,\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<FetchPageResult<string>> {\n const repos = this.settings.repoSlugs;\n if (repos.length === 0) {\n return { items: [], next: null };\n }\n const { idx, url: rawPageUrl } = decodePage(page);\n if (idx >= repos.length) {\n return { items: [], next: null };\n }\n const slug = repos[idx]!;\n const expectedPath = this.pipelinesPath(slug);\n const fetchUrl =\n this.sanitizeUrl(rawPageUrl, expectedPath) ??\n this.buildPipelinesUrl(slug, options);\n const res = await this.fetch<BitbucketPipelinesResponse>(\n fetchUrl,\n 'pipelines',\n signal,\n );\n const rows = res.body.values;\n const cutoff = options.since ? parseEpoch(options.since, 'iso') : null;\n let filtered: BitbucketPipeline[];\n let cutoffReached: boolean;\n if (cutoff !== null) {\n filtered = rows.filter((p) => {\n const ts = parseEpoch(p.created_on, 'iso');\n return ts === null || ts >= cutoff;\n });\n const last = rows.at(-1);\n const lastTs = last ? parseEpoch(last.created_on, 'iso') : null;\n cutoffReached = lastTs !== null && lastTs < cutoff;\n } else {\n filtered = rows;\n cutoffReached = false;\n }\n const safeNext = this.sanitizeUrl(res.body.next ?? null, expectedPath);\n const nextWithinRepo = cutoffReached ? null : safeNext;\n const batch: RepoBatch<BitbucketPipeline> = {\n repoSlug: slug,\n items: filtered,\n };\n if (nextWithinRepo !== null) {\n return { items: [batch], next: encodePage(idx, nextWithinRepo) };\n }\n const nextIdx = idx + 1;\n const next = nextIdx < repos.length ? encodePage(nextIdx, null) : null;\n return { items: [batch], next };\n }\n\n // -------------------------------------------------------------------------\n // Writers\n // -------------------------------------------------------------------------\n\n private async writePullRequests(\n storage: StorageHandle,\n items: unknown[],\n page: string | null,\n options: SyncOptions,\n ): Promise<void> {\n if (page === null && !options.since) {\n await storage.entities([], { types: ['pull_request'] });\n }\n const batches = items as RepoBatch<BitbucketPullRequest>[];\n for (const batch of batches) {\n for (const pr of batch.items) {\n const createdMs = parseEpoch(pr.created_on, 'iso');\n const updatedMs = parseEpoch(pr.updated_on, 'iso');\n if (createdMs === null || updatedMs === null) {\n continue;\n }\n await storage.entity({\n type: 'pull_request',\n id: `${this.settings.workspace}/${batch.repoSlug}:${pr.id}`,\n attributes: {\n workspace: this.settings.workspace,\n repo_slug: batch.repoSlug,\n pull_request_id: pr.id,\n title: pr.title,\n state: pr.state,\n author: pr.author?.nickname ?? pr.author?.display_name ?? null,\n source_branch: pr.source?.branch?.name ?? null,\n destination_branch: pr.destination?.branch?.name ?? null,\n web_url: pr.links?.html?.href ?? null,\n created_at: createdMs,\n closed_at: parseEpoch(pr.closed_on ?? null, 'iso'),\n },\n updated_at: updatedMs,\n });\n }\n }\n }\n\n private async writePipelines(\n storage: StorageHandle,\n items: unknown[],\n page: string | null,\n options: SyncOptions,\n ): Promise<void> {\n const pipelineAllowed = this.isResourceAllowed(\n 'pipeline',\n options.resources,\n );\n const eventAllowed = this.isResourceAllowed(\n 'pipeline_event',\n options.resources,\n );\n if (page === null && !options.since) {\n if (pipelineAllowed) {\n await storage.entities([], { types: ['pipeline'] });\n }\n if (eventAllowed) {\n await storage.events([], { names: ['pipeline_event'] });\n }\n }\n const batches = items as RepoBatch<BitbucketPipeline>[];\n for (const batch of batches) {\n for (const pipeline of batch.items) {\n const createdMs = parseEpoch(pipeline.created_on, 'iso');\n if (createdMs === null) {\n continue;\n }\n const completedMs = parseEpoch(pipeline.completed_on ?? null, 'iso');\n const durationMs =\n pipeline.duration_in_seconds !== null &&\n pipeline.duration_in_seconds !== undefined\n ? Math.round(pipeline.duration_in_seconds * 1000)\n : completedMs !== null\n ? completedMs - createdMs\n : null;\n const result = pipeline.state.result?.name ?? null;\n const refName = pipeline.target?.ref_name ?? null;\n const commitHash = pipeline.target?.commit?.hash ?? null;\n const triggerType = pipeline.trigger?.type ?? null;\n if (pipelineAllowed) {\n await storage.entity({\n type: 'pipeline',\n id: `${this.settings.workspace}/${batch.repoSlug}:${pipeline.uuid}`,\n attributes: {\n workspace: this.settings.workspace,\n repo_slug: batch.repoSlug,\n uuid: pipeline.uuid,\n build_number: pipeline.build_number,\n state: pipeline.state.name,\n result,\n ref_name: refName,\n commit: commitHash,\n trigger_type: triggerType,\n creator:\n pipeline.creator?.nickname ??\n pipeline.creator?.display_name ??\n null,\n created_at: createdMs,\n completed_at: completedMs,\n duration_ms: durationMs,\n },\n updated_at: completedMs ?? createdMs,\n });\n }\n if (eventAllowed) {\n await storage.event({\n name: 'pipeline_event',\n start_ts: createdMs,\n end_ts: completedMs,\n attributes: {\n workspace: this.settings.workspace,\n repo_slug: batch.repoSlug,\n uuid: pipeline.uuid,\n build_number: pipeline.build_number,\n state: pipeline.state.name,\n result,\n ref_name: refName,\n commit: commitHash,\n trigger_type: triggerType,\n duration_ms: durationMs,\n },\n });\n }\n }\n }\n }\n\n // -------------------------------------------------------------------------\n // Cursor resume\n // -------------------------------------------------------------------------\n\n private resolveCursor(cursor: unknown): BitbucketSyncCursor | undefined {\n if (!isBitbucketSyncCursor(cursor)) {\n return undefined;\n }\n return { phase: cursor.phase, page: cursor.page };\n }\n\n // -------------------------------------------------------------------------\n // sync()\n // -------------------------------------------------------------------------\n\n async sync(\n options: SyncOptions,\n storage: StorageHandle,\n signal?: AbortSignal,\n ): Promise<SyncResult> {\n const cursor = this.resolveCursor(options.cursor);\n const phases = this.activePhases(options.resources);\n return paginateChunked<BitbucketPhase, string>({\n phases,\n cursor,\n signal,\n logger: this.logger,\n fetchPage: async (phase, page, sig) => {\n switch (phase) {\n case 'pull_requests':\n return this.fetchPullRequestsPage(options, page, sig);\n case 'pipelines':\n return this.fetchPipelinesPage(options, page, sig);\n }\n },\n writeBatch: async (phase, items, page) => {\n switch (phase) {\n case 'pull_requests':\n return this.writePullRequests(storage, items, page, options);\n case 'pipelines':\n return this.writePipelines(storage, items, page, options);\n }\n },\n });\n }\n}\n","import { BitbucketConnector } from './bitbucket';\n\nexport {\n bitbucketResources as resources,\n BitbucketConnector,\n configFields,\n doc,\n id,\n} from './bitbucket';\nexport type { BitbucketResource, BitbucketSettings } from './bitbucket';\nexport default BitbucketConnector;\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rawdash/connector-bitbucket",
|
|
3
|
+
"version": "0.19.0",
|
|
4
|
+
"description": "Rawdash connector for Bitbucket Cloud — pull requests, pipelines, and pipeline state-transition events",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/rawdash/rawdash.git",
|
|
11
|
+
"directory": "packages/connectors/bitbucket"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"@rawdash/source": "./src/index.ts",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"lint": "eslint src",
|
|
29
|
+
"test": "vitest run"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@rawdash/core": "workspace:*",
|
|
33
|
+
"zod": "^4.4.3"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@rawdash/connector-shared": "workspace:*",
|
|
37
|
+
"@rawdash/connector-test-utils": "workspace:*",
|
|
38
|
+
"fast-check": "^4.8.0",
|
|
39
|
+
"tsup": "^8.0.0",
|
|
40
|
+
"typescript": "^5.7.2",
|
|
41
|
+
"vitest": "^4.1.4"
|
|
42
|
+
}
|
|
43
|
+
}
|