@rawdash/connector-vercel 0.15.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 +185 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +427 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# @rawdash/connector-vercel
|
|
2
|
+
|
|
3
|
+
Rawdash connector for [Vercel](https://vercel.com) — syncs projects, deployments, and deployment state-transition events into the six-shape storage model. Pairs with `@rawdash/connector-github` for "is this deploy healthy" widgets.
|
|
4
|
+
|
|
5
|
+
## Auth setup
|
|
6
|
+
|
|
7
|
+
The connector authenticates with a Vercel access token. Two flavours work:
|
|
8
|
+
|
|
9
|
+
- **Personal access token** (recommended for single-user installs): Vercel → **Account Settings → Tokens → Create**. Scope it to the team you want to sync, or leave it user-scoped.
|
|
10
|
+
- **Team access token**: Vercel → **Team Settings → Security → Tokens → Create**. You must set `teamId` in the config for the token to read team resources.
|
|
11
|
+
|
|
12
|
+
The token only needs read permissions for projects and deployments. No webhook configuration is required.
|
|
13
|
+
|
|
14
|
+
## Configuration
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { secret } from '@rawdash/core';
|
|
18
|
+
|
|
19
|
+
const vercel = {
|
|
20
|
+
name: 'vercel',
|
|
21
|
+
connectorId: 'vercel',
|
|
22
|
+
config: {
|
|
23
|
+
apiToken: secret('VERCEL_API_TOKEN'),
|
|
24
|
+
// teamId: 'team_abc123', // optional — needed for team tokens
|
|
25
|
+
// projects: ['prj_one', 'prj_two'], // optional — restrict deployments to specific projects
|
|
26
|
+
// resources: ['projects', 'deployments'], // optional — defaults to all three
|
|
27
|
+
// deploymentsLookbackDays: 30, // optional — backfill window for full sync (default 30)
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Register the connector class when mounting the engine:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { VercelConnector } from '@rawdash/connector-vercel';
|
|
36
|
+
import { mountEngine } from '@rawdash/hono';
|
|
37
|
+
|
|
38
|
+
mountEngine(config, { connectorRegistry: { vercel: VercelConnector } });
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Choosing resources
|
|
42
|
+
|
|
43
|
+
The connector exposes three resources, written across two internal sync phases:
|
|
44
|
+
|
|
45
|
+
| Resource | Phase | What gets written |
|
|
46
|
+
| ------------------- | ----------- | -------------------------------------------------------------------------------------------------------- |
|
|
47
|
+
| `projects` | projects | `vercel_project` entities, one per Vercel project |
|
|
48
|
+
| `deployments` | deployments | `vercel_deployment` entities, one per deployment, with build duration and git ref attributes |
|
|
49
|
+
| `deployment_events` | deployments | `vercel_deployment_event` events, one per deployment with `start_ts=createdAt`, `end_ts=ready` (or null) |
|
|
50
|
+
|
|
51
|
+
`deployment_events` shares the `deployments` phase because each event is derived from the same payload as its parent deployment entity. Enabling `deployment_events` without `deployments` still runs the deployments query (so the events have data to emit) but skips writing the deployment entities themselves.
|
|
52
|
+
|
|
53
|
+
### Example dashboard
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { defineConfig, defineDashboard, defineMetric } from '@rawdash/core';
|
|
57
|
+
|
|
58
|
+
export default defineConfig({
|
|
59
|
+
connectors: [vercel],
|
|
60
|
+
dashboards: {
|
|
61
|
+
deploys: defineDashboard({
|
|
62
|
+
widgets: {
|
|
63
|
+
deploys_today: {
|
|
64
|
+
kind: 'stat',
|
|
65
|
+
title: 'Deploys today',
|
|
66
|
+
metric: defineMetric({
|
|
67
|
+
connector: vercel,
|
|
68
|
+
shape: 'event',
|
|
69
|
+
name: 'vercel_deployment_event',
|
|
70
|
+
field: 'start_ts',
|
|
71
|
+
fn: 'count',
|
|
72
|
+
window: '24h',
|
|
73
|
+
}),
|
|
74
|
+
},
|
|
75
|
+
deploy_failure_rate_7d: {
|
|
76
|
+
kind: 'stat',
|
|
77
|
+
title: 'Failure rate (7d)',
|
|
78
|
+
metric: defineMetric({
|
|
79
|
+
connector: vercel,
|
|
80
|
+
shape: 'event',
|
|
81
|
+
name: 'vercel_deployment_event',
|
|
82
|
+
field: 'start_ts',
|
|
83
|
+
fn: 'count',
|
|
84
|
+
window: '7d',
|
|
85
|
+
filter: [{ field: 'state', op: 'eq', value: 'ERROR' }],
|
|
86
|
+
}),
|
|
87
|
+
},
|
|
88
|
+
deploys_by_project: {
|
|
89
|
+
kind: 'distribution',
|
|
90
|
+
title: 'Deploys by project',
|
|
91
|
+
metric: defineMetric({
|
|
92
|
+
connector: vercel,
|
|
93
|
+
shape: 'event',
|
|
94
|
+
name: 'vercel_deployment_event',
|
|
95
|
+
fn: 'count',
|
|
96
|
+
window: '7d',
|
|
97
|
+
groupBy: { field: 'projectId' },
|
|
98
|
+
}),
|
|
99
|
+
},
|
|
100
|
+
deploys_per_day: {
|
|
101
|
+
kind: 'timeseries',
|
|
102
|
+
title: 'Daily deploys',
|
|
103
|
+
window: '14d',
|
|
104
|
+
metric: defineMetric({
|
|
105
|
+
connector: vercel,
|
|
106
|
+
shape: 'event',
|
|
107
|
+
name: 'vercel_deployment_event',
|
|
108
|
+
field: 'start_ts',
|
|
109
|
+
fn: 'count',
|
|
110
|
+
window: '14d',
|
|
111
|
+
groupBy: { field: 'start_ts', granularity: 'day' },
|
|
112
|
+
}),
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
}),
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Data model
|
|
121
|
+
|
|
122
|
+
| Storage shape | Entity/event type | Key attributes |
|
|
123
|
+
| ------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
124
|
+
| entity | `vercel_project` | name, framework, accountId, createdAt, updatedAt |
|
|
125
|
+
| entity | `vercel_deployment` | deploymentId, name, url, state, target, projectId, creatorUid, creatorUsername, source, gitRef, gitSha, createdAt, buildingAt, readyAt, buildDurationMs |
|
|
126
|
+
| event | `vercel_deployment_event` | same attribute set as `vercel_deployment`. `start_ts = createdAt`, `end_ts = ready` (or `null` for in-flight builds). |
|
|
127
|
+
|
|
128
|
+
Timestamps are stored as Unix epoch milliseconds. `buildDurationMs` is computed as `ready - buildingAt` when both are present, otherwise `null`. The `gitRef` attribute prefers `meta.githubCommitRef`, falling back to `gitlabCommitRef`, `bitbucketCommitRef`, then `meta.branch`.
|
|
129
|
+
|
|
130
|
+
## Schemas
|
|
131
|
+
|
|
132
|
+
`VercelConnector.schemas` declares the Zod schema for each resource's raw API response. Used by the cloud shape-drift pipeline to populate `connector_baselines`, and by the package's property tests.
|
|
133
|
+
|
|
134
|
+
| Resource | Represents |
|
|
135
|
+
| ------------- | -------------------------- |
|
|
136
|
+
| `projects` | `GET /v9/projects` page |
|
|
137
|
+
| `deployments` | `GET /v6/deployments` page |
|
|
138
|
+
|
|
139
|
+
## Sync behaviour
|
|
140
|
+
|
|
141
|
+
- **Backfill** (`mode: 'full'`): paginates `/v9/projects` and `/v6/deployments` via Vercel's `pagination.next` cursor (page size 100), passing the returned millisecond timestamp back as the `until` query param. Project and deployment entity scopes (plus the `vercel_deployment_event` event scope) are cleared at the start of their phase so deletions in Vercel converge. Deployments are bounded by `deploymentsLookbackDays` (default 30) — the connector sets `since` on the first page to cap the backfill window.
|
|
142
|
+
- **Incremental** (`mode: 'latest'`): applies `since={ms}` to the deployments endpoint so only deployments newer than the last sync are pulled. Projects are still refreshed on every sync since the list is small.
|
|
143
|
+
- **Rate limits**: Vercel sends `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix seconds) — the connector reports the parsed state back to the host so the engine can budget future requests. 429 responses are surfaced as `RateLimitError` by the shared HTTP client.
|
|
144
|
+
- **Resumable**: every paginated phase yields a `{ phase, page }` cursor (`ChunkedSyncCursor<TPhase, TPage>`) where `page` is the sanitized pagination URL. Pagination URLs are validated on the way in — only `https://api.vercel.com/v9/projects` and `https://api.vercel.com/v6/deployments` are accepted — to prevent a malicious or corrupted cursor from steering a follow-up request elsewhere.
|
|
145
|
+
|
|
146
|
+
## Errors
|
|
147
|
+
|
|
148
|
+
`@rawdash/connector-shared` maps Vercel's HTTP responses to typed errors automatically:
|
|
149
|
+
|
|
150
|
+
- `401` / `403` → `AuthError` — host stops syncing until the token is replaced.
|
|
151
|
+
- `429` → `RateLimitError` — host backs off and reschedules.
|
|
152
|
+
- `5xx` → `TransientError` — host retries on the next tick.
|
|
153
|
+
|
|
154
|
+
## Out of scope (post-v0.1)
|
|
155
|
+
|
|
156
|
+
- **Web Vitals / Speed Insights** — Vercel does not expose aggregated p75 metrics via the public REST API; the dashboard surface and Insights API require a different access pattern (per-page-view event ingest). Tracking as a follow-up.
|
|
157
|
+
- **Edge function logs** — high volume, low signal for a dashboard widget. Use the Vercel log drain integration instead.
|
|
158
|
+
- **DNS / Domain APIs** — not dashboard-shaped.
|
|
159
|
+
|
|
160
|
+
## Registering in the MCP server
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { VercelConnector, configFields } from '@rawdash/connector-vercel';
|
|
164
|
+
|
|
165
|
+
createMcpServer({
|
|
166
|
+
// ...
|
|
167
|
+
connectorFactories: [
|
|
168
|
+
{
|
|
169
|
+
id: 'vercel',
|
|
170
|
+
configFields,
|
|
171
|
+
create: VercelConnector.create,
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Property tests
|
|
178
|
+
|
|
179
|
+
Resources in this connector have fast-check property tests under `src/property.test.ts` that:
|
|
180
|
+
|
|
181
|
+
1. Generate synthetic API payloads from a Zod schema mirroring Vercel's response shape.
|
|
182
|
+
2. Pipe them through `connector.sync()` against an `InMemoryStorage` instance.
|
|
183
|
+
3. Assert universal invariants — non-empty entity ids, finite event timestamps, no `undefined` reaching storage, no thrown errors on any valid input — plus per-resource counts.
|
|
184
|
+
|
|
185
|
+
The helper lives in `@rawdash/connector-test-utils`. When adding a new resource, add a Zod schema for its payload and a test wired up via `runPropertySyncTest`.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { BaseConnector, ConnectorContext, SyncOptions, StorageHandle, SyncResult } from '@rawdash/core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
declare const configFields: z.ZodObject<{
|
|
5
|
+
apiToken: z.ZodObject<{
|
|
6
|
+
$secret: z.ZodString;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
teamId: z.ZodOptional<z.ZodString>;
|
|
9
|
+
projects: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
10
|
+
resources: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
11
|
+
projects: "projects";
|
|
12
|
+
deployments: "deployments";
|
|
13
|
+
deployment_events: "deployment_events";
|
|
14
|
+
}>>>;
|
|
15
|
+
deploymentsLookbackDays: z.ZodOptional<z.ZodNumber>;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
type VercelResource = 'projects' | 'deployments' | 'deployment_events';
|
|
18
|
+
interface VercelSettings {
|
|
19
|
+
teamId?: string;
|
|
20
|
+
projects?: readonly string[];
|
|
21
|
+
resources?: readonly VercelResource[];
|
|
22
|
+
deploymentsLookbackDays?: number;
|
|
23
|
+
}
|
|
24
|
+
declare const vercelCredentials: {
|
|
25
|
+
apiToken: {
|
|
26
|
+
description: string;
|
|
27
|
+
auth: "required";
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
type VercelCredentials = typeof vercelCredentials;
|
|
31
|
+
declare class VercelConnector extends BaseConnector<VercelSettings, VercelCredentials> {
|
|
32
|
+
static readonly id = "vercel";
|
|
33
|
+
static readonly schemas: {
|
|
34
|
+
readonly projects: z.ZodObject<{
|
|
35
|
+
projects: z.ZodArray<z.ZodObject<{
|
|
36
|
+
id: z.ZodString;
|
|
37
|
+
name: z.ZodString;
|
|
38
|
+
framework: z.ZodNullable<z.ZodString>;
|
|
39
|
+
createdAt: z.ZodNumber;
|
|
40
|
+
updatedAt: z.ZodNumber;
|
|
41
|
+
}, z.core.$strip>>;
|
|
42
|
+
pagination: z.ZodObject<{
|
|
43
|
+
count: z.ZodNumber;
|
|
44
|
+
next: z.ZodNullable<z.ZodNumber>;
|
|
45
|
+
}, z.core.$strip>;
|
|
46
|
+
}, z.core.$strip>;
|
|
47
|
+
readonly deployments: z.ZodObject<{
|
|
48
|
+
deployments: z.ZodArray<z.ZodObject<{
|
|
49
|
+
uid: z.ZodString;
|
|
50
|
+
name: z.ZodString;
|
|
51
|
+
url: z.ZodString;
|
|
52
|
+
created: z.ZodNumber;
|
|
53
|
+
state: z.ZodEnum<{
|
|
54
|
+
BUILDING: "BUILDING";
|
|
55
|
+
ERROR: "ERROR";
|
|
56
|
+
INITIALIZING: "INITIALIZING";
|
|
57
|
+
QUEUED: "QUEUED";
|
|
58
|
+
READY: "READY";
|
|
59
|
+
CANCELED: "CANCELED";
|
|
60
|
+
}>;
|
|
61
|
+
target: z.ZodNullable<z.ZodEnum<{
|
|
62
|
+
production: "production";
|
|
63
|
+
staging: "staging";
|
|
64
|
+
preview: "preview";
|
|
65
|
+
}>>;
|
|
66
|
+
creator: z.ZodObject<{
|
|
67
|
+
uid: z.ZodString;
|
|
68
|
+
username: z.ZodNullable<z.ZodString>;
|
|
69
|
+
}, z.core.$strip>;
|
|
70
|
+
buildingAt: z.ZodNullable<z.ZodNumber>;
|
|
71
|
+
ready: z.ZodNullable<z.ZodNumber>;
|
|
72
|
+
source: z.ZodNullable<z.ZodString>;
|
|
73
|
+
meta: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodString>>>>;
|
|
74
|
+
projectId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
75
|
+
}, z.core.$strip>>;
|
|
76
|
+
pagination: z.ZodObject<{
|
|
77
|
+
count: z.ZodNumber;
|
|
78
|
+
next: z.ZodNullable<z.ZodNumber>;
|
|
79
|
+
}, z.core.$strip>;
|
|
80
|
+
}, z.core.$strip>;
|
|
81
|
+
};
|
|
82
|
+
static create(input: unknown, ctx?: ConnectorContext): VercelConnector;
|
|
83
|
+
readonly id = "vercel";
|
|
84
|
+
readonly credentials: {
|
|
85
|
+
apiToken: {
|
|
86
|
+
description: string;
|
|
87
|
+
auth: "required";
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
private buildHeaders;
|
|
91
|
+
private fetch;
|
|
92
|
+
private isResourceEnabled;
|
|
93
|
+
private activePhases;
|
|
94
|
+
private allowedPagePath;
|
|
95
|
+
private sanitizePageUrl;
|
|
96
|
+
private resolveCursor;
|
|
97
|
+
private withTeamId;
|
|
98
|
+
private buildInitialProjectsUrl;
|
|
99
|
+
private buildInitialDeploymentsUrl;
|
|
100
|
+
private computeDeploymentsSinceMs;
|
|
101
|
+
private buildNextPageUrl;
|
|
102
|
+
private fetchProjectsPage;
|
|
103
|
+
private fetchDeploymentsPage;
|
|
104
|
+
private writeProjects;
|
|
105
|
+
private writeDeployments;
|
|
106
|
+
sync(options: SyncOptions, storage: StorageHandle, signal?: AbortSignal): Promise<SyncResult>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export { VercelConnector, type VercelResource, type VercelSettings, configFields, VercelConnector as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
// src/vercel.ts
|
|
2
|
+
import {
|
|
3
|
+
BaseConnector,
|
|
4
|
+
defineConfigFields,
|
|
5
|
+
paginateChunked
|
|
6
|
+
} from "@rawdash/core";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
var configFields = defineConfigFields(
|
|
9
|
+
z.object({
|
|
10
|
+
apiToken: z.object({ $secret: z.string() }).meta({
|
|
11
|
+
label: "API Token",
|
|
12
|
+
description: "Vercel access token (Personal or Team). Create one at Vercel \u2192 Account Settings \u2192 Tokens.",
|
|
13
|
+
placeholder: "vercel_token",
|
|
14
|
+
secret: true
|
|
15
|
+
}),
|
|
16
|
+
teamId: z.string().min(1).optional().meta({
|
|
17
|
+
label: "Team ID (optional)",
|
|
18
|
+
description: "Vercel team ID (slug or `team_...`). Omit to use the token owner scope. Required if the token is a team token.",
|
|
19
|
+
placeholder: "team_abc123"
|
|
20
|
+
}),
|
|
21
|
+
projects: z.array(z.string().min(1)).nonempty().optional().meta({
|
|
22
|
+
label: "Projects (optional)",
|
|
23
|
+
description: "Restrict deployment sync to specific Vercel project IDs (e.g. `prj_...`). Omit to sync every project the token can see."
|
|
24
|
+
}),
|
|
25
|
+
resources: z.array(z.enum(["projects", "deployments", "deployment_events"])).nonempty().optional().meta({
|
|
26
|
+
label: "Resources",
|
|
27
|
+
description: "Which Vercel resources to sync. Omit to sync all of them. 'deployment_events' depends on 'deployments' being fetched \u2014 enabling it without 'deployments' still runs the deployments query, but skips writing deployment entities."
|
|
28
|
+
}),
|
|
29
|
+
deploymentsLookbackDays: z.number().int().positive().max(365).optional().meta({
|
|
30
|
+
label: "Deployments lookback (days)",
|
|
31
|
+
description: "How many days back to fetch deployments on a full sync. Defaults to 30. Vercel returns deployments newest-first; this caps the backfill window.",
|
|
32
|
+
placeholder: "30"
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
);
|
|
36
|
+
var vercelCredentials = {
|
|
37
|
+
apiToken: {
|
|
38
|
+
description: "Vercel access token",
|
|
39
|
+
auth: "required"
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var vercelRateLimit = {
|
|
43
|
+
parse(h) {
|
|
44
|
+
const remainingRaw = h.get("x-ratelimit-remaining");
|
|
45
|
+
const resetRaw = h.get("x-ratelimit-reset");
|
|
46
|
+
if (remainingRaw === null || resetRaw === null) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const remaining = Number(remainingRaw);
|
|
50
|
+
const reset = Number(resetRaw);
|
|
51
|
+
if (!Number.isFinite(remaining) || !Number.isFinite(reset) || reset < 0) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return { remaining, resetAt: new Date(reset * 1e3) };
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var PHASE_ORDER = ["projects", "deployments"];
|
|
58
|
+
function isVercelSyncCursor(value) {
|
|
59
|
+
if (typeof value !== "object" || value === null) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const v = value;
|
|
63
|
+
if (typeof v.phase !== "string") {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
if (!PHASE_ORDER.includes(v.phase)) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
if (v.page !== null && typeof v.page !== "string") {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
var idString = z.string().min(1);
|
|
75
|
+
var nonNegInt = z.number().int().nonnegative();
|
|
76
|
+
var paginationSchema = z.object({
|
|
77
|
+
count: nonNegInt,
|
|
78
|
+
next: nonNegInt.nullable()
|
|
79
|
+
});
|
|
80
|
+
var projectSchema = z.object({
|
|
81
|
+
id: idString,
|
|
82
|
+
name: z.string().min(1),
|
|
83
|
+
framework: z.string().nullable(),
|
|
84
|
+
createdAt: nonNegInt,
|
|
85
|
+
updatedAt: nonNegInt
|
|
86
|
+
});
|
|
87
|
+
var projectsResponseSchema = z.object({
|
|
88
|
+
projects: z.array(projectSchema),
|
|
89
|
+
pagination: paginationSchema
|
|
90
|
+
});
|
|
91
|
+
var deploymentStateSchema = z.enum([
|
|
92
|
+
"BUILDING",
|
|
93
|
+
"ERROR",
|
|
94
|
+
"INITIALIZING",
|
|
95
|
+
"QUEUED",
|
|
96
|
+
"READY",
|
|
97
|
+
"CANCELED"
|
|
98
|
+
]);
|
|
99
|
+
var deploymentTargetSchema = z.enum(["production", "staging", "preview"]).nullable();
|
|
100
|
+
var deploymentSchema = z.object({
|
|
101
|
+
uid: idString,
|
|
102
|
+
name: z.string(),
|
|
103
|
+
url: z.string(),
|
|
104
|
+
created: nonNegInt,
|
|
105
|
+
state: deploymentStateSchema,
|
|
106
|
+
target: deploymentTargetSchema,
|
|
107
|
+
creator: z.object({
|
|
108
|
+
uid: idString,
|
|
109
|
+
username: z.string().nullable()
|
|
110
|
+
}),
|
|
111
|
+
buildingAt: nonNegInt.nullable(),
|
|
112
|
+
ready: nonNegInt.nullable(),
|
|
113
|
+
source: z.string().nullable(),
|
|
114
|
+
meta: z.record(z.string(), z.string().nullable()).nullable().optional(),
|
|
115
|
+
projectId: z.string().nullable().optional()
|
|
116
|
+
});
|
|
117
|
+
var deploymentsResponseSchema = z.object({
|
|
118
|
+
deployments: z.array(deploymentSchema),
|
|
119
|
+
pagination: paginationSchema
|
|
120
|
+
});
|
|
121
|
+
var VERCEL_API_HOST = "api.vercel.com";
|
|
122
|
+
var VERCEL_API_BASE = `https://${VERCEL_API_HOST}`;
|
|
123
|
+
var PROJECTS_PAGE_SIZE = 100;
|
|
124
|
+
var DEPLOYMENTS_PAGE_SIZE = 100;
|
|
125
|
+
var DEFAULT_LOOKBACK_DAYS = 30;
|
|
126
|
+
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
127
|
+
var VercelConnector = class _VercelConnector extends BaseConnector {
|
|
128
|
+
static id = "vercel";
|
|
129
|
+
static schemas = {
|
|
130
|
+
projects: projectsResponseSchema,
|
|
131
|
+
deployments: deploymentsResponseSchema
|
|
132
|
+
};
|
|
133
|
+
static create(input, ctx) {
|
|
134
|
+
const parsed = configFields.parse(input);
|
|
135
|
+
return new _VercelConnector(
|
|
136
|
+
{
|
|
137
|
+
teamId: parsed.teamId,
|
|
138
|
+
projects: parsed.projects,
|
|
139
|
+
resources: parsed.resources,
|
|
140
|
+
deploymentsLookbackDays: parsed.deploymentsLookbackDays
|
|
141
|
+
},
|
|
142
|
+
{ apiToken: parsed.apiToken },
|
|
143
|
+
ctx
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
id = "vercel";
|
|
147
|
+
credentials = vercelCredentials;
|
|
148
|
+
buildHeaders() {
|
|
149
|
+
return {
|
|
150
|
+
Authorization: `Bearer ${this.creds.apiToken}`,
|
|
151
|
+
"User-Agent": "rawdash/connector-vercel (+https://rawdash.dev)"
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
fetch(url, resource, signal) {
|
|
155
|
+
return this.get(url, {
|
|
156
|
+
resource,
|
|
157
|
+
headers: this.buildHeaders(),
|
|
158
|
+
signal,
|
|
159
|
+
rateLimit: vercelRateLimit
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// -------------------------------------------------------------------------
|
|
163
|
+
// Resource enablement
|
|
164
|
+
// -------------------------------------------------------------------------
|
|
165
|
+
isResourceEnabled(resource) {
|
|
166
|
+
const enabled = this.settings.resources;
|
|
167
|
+
if (!enabled || enabled.length === 0) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
return enabled.includes(resource);
|
|
171
|
+
}
|
|
172
|
+
activePhases() {
|
|
173
|
+
const phases = [];
|
|
174
|
+
if (this.isResourceEnabled("projects")) {
|
|
175
|
+
phases.push("projects");
|
|
176
|
+
}
|
|
177
|
+
if (this.isResourceEnabled("deployments") || this.isResourceEnabled("deployment_events")) {
|
|
178
|
+
phases.push("deployments");
|
|
179
|
+
}
|
|
180
|
+
return phases;
|
|
181
|
+
}
|
|
182
|
+
// -------------------------------------------------------------------------
|
|
183
|
+
// URL building + sanitization
|
|
184
|
+
// -------------------------------------------------------------------------
|
|
185
|
+
allowedPagePath(phase) {
|
|
186
|
+
switch (phase) {
|
|
187
|
+
case "projects":
|
|
188
|
+
return "/v9/projects";
|
|
189
|
+
case "deployments":
|
|
190
|
+
return "/v6/deployments";
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
sanitizePageUrl(phase, pageUrl) {
|
|
194
|
+
if (pageUrl === null) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
const allowedPath = this.allowedPagePath(phase);
|
|
198
|
+
try {
|
|
199
|
+
const u = new URL(pageUrl);
|
|
200
|
+
if (u.protocol !== "https:" || u.host !== VERCEL_API_HOST || u.pathname !== allowedPath) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
return u.toString();
|
|
204
|
+
} catch {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
resolveCursor(cursor) {
|
|
209
|
+
if (!isVercelSyncCursor(cursor)) {
|
|
210
|
+
return void 0;
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
phase: cursor.phase,
|
|
214
|
+
page: this.sanitizePageUrl(cursor.phase, cursor.page)
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
withTeamId(u) {
|
|
218
|
+
if (this.settings.teamId !== void 0) {
|
|
219
|
+
u.searchParams.set("teamId", this.settings.teamId);
|
|
220
|
+
}
|
|
221
|
+
return u;
|
|
222
|
+
}
|
|
223
|
+
buildInitialProjectsUrl() {
|
|
224
|
+
const u = new URL(`${VERCEL_API_BASE}/v9/projects`);
|
|
225
|
+
u.searchParams.set("limit", String(PROJECTS_PAGE_SIZE));
|
|
226
|
+
this.withTeamId(u);
|
|
227
|
+
return u.toString();
|
|
228
|
+
}
|
|
229
|
+
buildInitialDeploymentsUrl(options) {
|
|
230
|
+
const u = new URL(`${VERCEL_API_BASE}/v6/deployments`);
|
|
231
|
+
u.searchParams.set("limit", String(DEPLOYMENTS_PAGE_SIZE));
|
|
232
|
+
for (const project of this.settings.projects ?? []) {
|
|
233
|
+
u.searchParams.append("projectId", project);
|
|
234
|
+
}
|
|
235
|
+
const sinceMs = this.computeDeploymentsSinceMs(options);
|
|
236
|
+
if (sinceMs !== null) {
|
|
237
|
+
u.searchParams.set("since", String(sinceMs));
|
|
238
|
+
}
|
|
239
|
+
this.withTeamId(u);
|
|
240
|
+
return u.toString();
|
|
241
|
+
}
|
|
242
|
+
computeDeploymentsSinceMs(options) {
|
|
243
|
+
if (options.mode === "latest" && options.since) {
|
|
244
|
+
const ms = new Date(options.since).getTime();
|
|
245
|
+
if (Number.isFinite(ms)) {
|
|
246
|
+
return ms;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const days = this.settings.deploymentsLookbackDays ?? DEFAULT_LOOKBACK_DAYS;
|
|
250
|
+
return Date.now() - days * MS_PER_DAY;
|
|
251
|
+
}
|
|
252
|
+
// -------------------------------------------------------------------------
|
|
253
|
+
// Fetchers
|
|
254
|
+
// -------------------------------------------------------------------------
|
|
255
|
+
buildNextPageUrl(phase, currentUrl, until) {
|
|
256
|
+
const u = new URL(currentUrl);
|
|
257
|
+
u.searchParams.set("until", String(until));
|
|
258
|
+
return u.toString();
|
|
259
|
+
}
|
|
260
|
+
async fetchProjectsPage(page, signal) {
|
|
261
|
+
const url = page ?? this.buildInitialProjectsUrl();
|
|
262
|
+
const res = await this.fetch(
|
|
263
|
+
url,
|
|
264
|
+
"projects",
|
|
265
|
+
signal
|
|
266
|
+
);
|
|
267
|
+
const next = res.body.pagination.next !== null && res.body.pagination.next !== void 0 ? this.sanitizePageUrl(
|
|
268
|
+
"projects",
|
|
269
|
+
this.buildNextPageUrl("projects", url, res.body.pagination.next)
|
|
270
|
+
) : null;
|
|
271
|
+
return { items: res.body.projects, next };
|
|
272
|
+
}
|
|
273
|
+
async fetchDeploymentsPage(page, options, signal) {
|
|
274
|
+
const url = page ?? this.buildInitialDeploymentsUrl(options);
|
|
275
|
+
const res = await this.fetch(
|
|
276
|
+
url,
|
|
277
|
+
"deployments",
|
|
278
|
+
signal
|
|
279
|
+
);
|
|
280
|
+
const next = res.body.pagination.next !== null && res.body.pagination.next !== void 0 ? this.sanitizePageUrl(
|
|
281
|
+
"deployments",
|
|
282
|
+
this.buildNextPageUrl("deployments", url, res.body.pagination.next)
|
|
283
|
+
) : null;
|
|
284
|
+
return { items: res.body.deployments, next };
|
|
285
|
+
}
|
|
286
|
+
// -------------------------------------------------------------------------
|
|
287
|
+
// Writers
|
|
288
|
+
// -------------------------------------------------------------------------
|
|
289
|
+
async writeProjects(storage, projects) {
|
|
290
|
+
for (const p of projects) {
|
|
291
|
+
const createdMs = Number.isFinite(p.createdAt) ? p.createdAt : null;
|
|
292
|
+
const updatedMs = Number.isFinite(p.updatedAt) ? p.updatedAt : null;
|
|
293
|
+
if (createdMs === null || updatedMs === null) {
|
|
294
|
+
console.warn(
|
|
295
|
+
`[connector-vercel] skipping project ${p.id} with unparseable timestamps`
|
|
296
|
+
);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
await storage.entity({
|
|
300
|
+
type: "vercel_project",
|
|
301
|
+
id: p.id,
|
|
302
|
+
attributes: {
|
|
303
|
+
name: p.name,
|
|
304
|
+
framework: p.framework,
|
|
305
|
+
accountId: p.accountId ?? null,
|
|
306
|
+
createdAt: createdMs,
|
|
307
|
+
updatedAt: updatedMs
|
|
308
|
+
},
|
|
309
|
+
updated_at: updatedMs
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
async writeDeployments(storage, deployments) {
|
|
314
|
+
const writeEntities = this.isResourceEnabled("deployments");
|
|
315
|
+
const writeEvents = this.isResourceEnabled("deployment_events");
|
|
316
|
+
for (const d of deployments) {
|
|
317
|
+
const createdMs = Number.isFinite(d.created) ? d.created : null;
|
|
318
|
+
if (createdMs === null) {
|
|
319
|
+
console.warn(
|
|
320
|
+
`[connector-vercel] skipping deployment ${d.uid} with unparseable created timestamp`
|
|
321
|
+
);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const buildingMs = d.buildingAt !== null && d.buildingAt !== void 0 && Number.isFinite(d.buildingAt) ? d.buildingAt : null;
|
|
325
|
+
const readyMs = d.ready !== null && d.ready !== void 0 && Number.isFinite(d.ready) ? d.ready : null;
|
|
326
|
+
const buildDurationMs = readyMs !== null && buildingMs !== null && readyMs >= buildingMs ? readyMs - buildingMs : null;
|
|
327
|
+
const gitRef = d.meta?.githubCommitRef ?? d.meta?.gitlabCommitRef ?? d.meta?.bitbucketCommitRef ?? d.meta?.branch ?? null;
|
|
328
|
+
const gitSha = d.meta?.githubCommitSha ?? null;
|
|
329
|
+
const projectId = d.projectId ?? null;
|
|
330
|
+
const target = d.target ?? null;
|
|
331
|
+
const creatorUsername = d.creator.username ?? null;
|
|
332
|
+
const baseAttributes = {
|
|
333
|
+
deploymentId: d.uid,
|
|
334
|
+
name: d.name,
|
|
335
|
+
url: d.url,
|
|
336
|
+
state: d.state,
|
|
337
|
+
target,
|
|
338
|
+
projectId,
|
|
339
|
+
creatorUid: d.creator.uid,
|
|
340
|
+
creatorUsername,
|
|
341
|
+
source: d.source ?? null,
|
|
342
|
+
gitRef,
|
|
343
|
+
gitSha,
|
|
344
|
+
createdAt: createdMs,
|
|
345
|
+
buildingAt: buildingMs,
|
|
346
|
+
readyAt: readyMs,
|
|
347
|
+
buildDurationMs
|
|
348
|
+
};
|
|
349
|
+
if (writeEntities) {
|
|
350
|
+
await storage.entity({
|
|
351
|
+
type: "vercel_deployment",
|
|
352
|
+
id: d.uid,
|
|
353
|
+
attributes: baseAttributes,
|
|
354
|
+
updated_at: readyMs ?? buildingMs ?? createdMs
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
if (writeEvents) {
|
|
358
|
+
await storage.event({
|
|
359
|
+
name: "vercel_deployment_event",
|
|
360
|
+
start_ts: createdMs,
|
|
361
|
+
end_ts: readyMs,
|
|
362
|
+
attributes: baseAttributes
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// -------------------------------------------------------------------------
|
|
368
|
+
// sync
|
|
369
|
+
// -------------------------------------------------------------------------
|
|
370
|
+
async sync(options, storage, signal) {
|
|
371
|
+
const cursor = this.resolveCursor(options.cursor);
|
|
372
|
+
const isFull = options.mode === "full";
|
|
373
|
+
const phases = this.activePhases();
|
|
374
|
+
return paginateChunked({
|
|
375
|
+
phases,
|
|
376
|
+
cursor,
|
|
377
|
+
signal,
|
|
378
|
+
fetchPage: async (phase, page, sig) => {
|
|
379
|
+
switch (phase) {
|
|
380
|
+
case "projects":
|
|
381
|
+
return this.fetchProjectsPage(page, sig);
|
|
382
|
+
case "deployments":
|
|
383
|
+
return this.fetchDeploymentsPage(page, options, sig);
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
writeBatch: async (phase, items, page) => {
|
|
387
|
+
if (isFull && page === null) {
|
|
388
|
+
switch (phase) {
|
|
389
|
+
case "projects":
|
|
390
|
+
if (this.isResourceEnabled("projects")) {
|
|
391
|
+
await storage.entities([], { types: ["vercel_project"] });
|
|
392
|
+
}
|
|
393
|
+
break;
|
|
394
|
+
case "deployments":
|
|
395
|
+
if (this.isResourceEnabled("deployments")) {
|
|
396
|
+
await storage.entities([], { types: ["vercel_deployment"] });
|
|
397
|
+
}
|
|
398
|
+
if (this.isResourceEnabled("deployment_events")) {
|
|
399
|
+
await storage.events([], {
|
|
400
|
+
names: ["vercel_deployment_event"]
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
switch (phase) {
|
|
407
|
+
case "projects":
|
|
408
|
+
if (!this.isResourceEnabled("projects")) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
return this.writeProjects(storage, items);
|
|
412
|
+
case "deployments":
|
|
413
|
+
return this.writeDeployments(storage, items);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// src/index.ts
|
|
421
|
+
var index_default = VercelConnector;
|
|
422
|
+
export {
|
|
423
|
+
VercelConnector,
|
|
424
|
+
configFields,
|
|
425
|
+
index_default as default
|
|
426
|
+
};
|
|
427
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/vercel.ts","../src/index.ts"],"sourcesContent":["import {\n type HttpResponse,\n type RateLimitPolicy,\n} from '@rawdash/connector-shared';\nimport {\n BaseConnector,\n type ChunkedSyncCursor,\n type ConnectorContext,\n type CredentialsSchema,\n type JSONValue,\n type StorageHandle,\n type SyncOptions,\n type SyncResult,\n defineConfigFields,\n paginateChunked,\n} from '@rawdash/core';\nimport { z } from 'zod';\n\n// ---------------------------------------------------------------------------\n// configFields\n// ---------------------------------------------------------------------------\n\nexport const configFields = defineConfigFields(\n z.object({\n apiToken: z.object({ $secret: z.string() }).meta({\n label: 'API Token',\n description:\n 'Vercel access token (Personal or Team). Create one at Vercel → Account Settings → Tokens.',\n placeholder: 'vercel_token',\n secret: true,\n }),\n teamId: z.string().min(1).optional().meta({\n label: 'Team ID (optional)',\n description:\n 'Vercel team ID (slug or `team_...`). Omit to use the token owner scope. Required if the token is a team token.',\n placeholder: 'team_abc123',\n }),\n projects: z.array(z.string().min(1)).nonempty().optional().meta({\n label: 'Projects (optional)',\n description:\n 'Restrict deployment sync to specific Vercel project IDs (e.g. `prj_...`). Omit to sync every project the token can see.',\n }),\n resources: z\n .array(z.enum(['projects', 'deployments', 'deployment_events']))\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n \"Which Vercel resources to sync. Omit to sync all of them. 'deployment_events' depends on 'deployments' being fetched — enabling it without 'deployments' still runs the deployments query, but skips writing deployment entities.\",\n }),\n deploymentsLookbackDays: z\n .number()\n .int()\n .positive()\n .max(365)\n .optional()\n .meta({\n label: 'Deployments lookback (days)',\n description:\n 'How many days back to fetch deployments on a full sync. Defaults to 30. Vercel returns deployments newest-first; this caps the backfill window.',\n placeholder: '30',\n }),\n }),\n);\n\nexport type VercelResource = 'projects' | 'deployments' | 'deployment_events';\n\nexport interface VercelSettings {\n teamId?: string;\n projects?: readonly string[];\n resources?: readonly VercelResource[];\n deploymentsLookbackDays?: number;\n}\n\nconst vercelCredentials = {\n apiToken: {\n description: 'Vercel access token',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype VercelCredentials = typeof vercelCredentials;\n\n// ---------------------------------------------------------------------------\n// Rate-limit policy — Vercel sends standard `X-RateLimit-*` headers, reset is\n// a Unix timestamp in seconds.\n// ---------------------------------------------------------------------------\n\nconst vercelRateLimit: RateLimitPolicy = {\n parse(h) {\n const remainingRaw = h.get('x-ratelimit-remaining');\n const resetRaw = h.get('x-ratelimit-reset');\n if (remainingRaw === null || resetRaw === null) {\n return null;\n }\n const remaining = Number(remainingRaw);\n const reset = Number(resetRaw);\n if (!Number.isFinite(remaining) || !Number.isFinite(reset) || reset < 0) {\n return null;\n }\n return { remaining, resetAt: new Date(reset * 1000) };\n },\n};\n\n// ---------------------------------------------------------------------------\n// Sync phases + cursor\n// ---------------------------------------------------------------------------\n\nconst PHASE_ORDER = ['projects', 'deployments'] as const;\n\ntype VercelPhase = (typeof PHASE_ORDER)[number];\n\ntype VercelSyncCursor = ChunkedSyncCursor<VercelPhase, string>;\n\nfunction isVercelSyncCursor(value: unknown): value is VercelSyncCursor {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const v = value as { phase?: unknown; page?: unknown };\n if (typeof v.phase !== 'string') {\n return false;\n }\n if (!(PHASE_ORDER as readonly string[]).includes(v.phase)) {\n return false;\n }\n if (v.page !== null && typeof v.page !== 'string') {\n return false;\n }\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Vercel API types\n// ---------------------------------------------------------------------------\n\ninterface VercelProject {\n id: string;\n name: string;\n accountId?: string;\n framework: string | null;\n createdAt: number;\n updatedAt: number;\n}\n\ninterface VercelProjectsResponse {\n projects: VercelProject[];\n pagination: VercelPagination;\n}\n\ninterface VercelPagination {\n count: number;\n next: number | null;\n prev?: number | null;\n}\n\ninterface VercelDeploymentCreator {\n uid: string;\n username?: string | null;\n email?: string | null;\n}\n\ninterface VercelDeploymentMeta {\n githubCommitRef?: string;\n githubCommitSha?: string;\n githubCommitMessage?: string;\n gitlabCommitRef?: string;\n bitbucketCommitRef?: string;\n branch?: string;\n}\n\ninterface VercelDeployment {\n uid: string;\n name: string;\n url: string;\n created: number;\n createdAt?: number;\n state:\n | 'BUILDING'\n | 'ERROR'\n | 'INITIALIZING'\n | 'QUEUED'\n | 'READY'\n | 'CANCELED';\n target: 'production' | 'staging' | 'preview' | null;\n inspectorUrl?: string | null;\n creator: VercelDeploymentCreator;\n buildingAt?: number | null;\n ready?: number | null;\n source?: 'cli' | 'git' | 'import' | 'api-trigger-git-deploy' | string | null;\n meta?: VercelDeploymentMeta | null;\n projectId?: string;\n}\n\ninterface VercelDeploymentsResponse {\n deployments: VercelDeployment[];\n pagination: VercelPagination;\n}\n\n// ---------------------------------------------------------------------------\n// Schemas — describe the per-resource API response shape consumed by request()\n// ---------------------------------------------------------------------------\n\nconst idString = z.string().min(1);\nconst nonNegInt = z.number().int().nonnegative();\n\nconst paginationSchema = z.object({\n count: nonNegInt,\n next: nonNegInt.nullable(),\n});\n\nconst projectSchema = z.object({\n id: idString,\n name: z.string().min(1),\n framework: z.string().nullable(),\n createdAt: nonNegInt,\n updatedAt: nonNegInt,\n});\n\nconst projectsResponseSchema = z.object({\n projects: z.array(projectSchema),\n pagination: paginationSchema,\n});\n\nconst deploymentStateSchema = z.enum([\n 'BUILDING',\n 'ERROR',\n 'INITIALIZING',\n 'QUEUED',\n 'READY',\n 'CANCELED',\n]);\n\nconst deploymentTargetSchema = z\n .enum(['production', 'staging', 'preview'])\n .nullable();\n\nconst deploymentSchema = z.object({\n uid: idString,\n name: z.string(),\n url: z.string(),\n created: nonNegInt,\n state: deploymentStateSchema,\n target: deploymentTargetSchema,\n creator: z.object({\n uid: idString,\n username: z.string().nullable(),\n }),\n buildingAt: nonNegInt.nullable(),\n ready: nonNegInt.nullable(),\n source: z.string().nullable(),\n meta: z.record(z.string(), z.string().nullable()).nullable().optional(),\n projectId: z.string().nullable().optional(),\n});\n\nconst deploymentsResponseSchema = z.object({\n deployments: z.array(deploymentSchema),\n pagination: paginationSchema,\n});\n\n// ---------------------------------------------------------------------------\n// VercelConnector\n// ---------------------------------------------------------------------------\n\nconst VERCEL_API_HOST = 'api.vercel.com';\nconst VERCEL_API_BASE = `https://${VERCEL_API_HOST}`;\nconst PROJECTS_PAGE_SIZE = 100;\nconst DEPLOYMENTS_PAGE_SIZE = 100;\nconst DEFAULT_LOOKBACK_DAYS = 30;\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\n\nexport class VercelConnector extends BaseConnector<\n VercelSettings,\n VercelCredentials\n> {\n static readonly id = 'vercel';\n\n static readonly schemas = {\n projects: projectsResponseSchema,\n deployments: deploymentsResponseSchema,\n } as const;\n\n static create(input: unknown, ctx?: ConnectorContext): VercelConnector {\n const parsed = configFields.parse(input);\n return new VercelConnector(\n {\n teamId: parsed.teamId,\n projects: parsed.projects,\n resources: parsed.resources,\n deploymentsLookbackDays: parsed.deploymentsLookbackDays,\n },\n { apiToken: parsed.apiToken },\n ctx,\n );\n }\n\n readonly id = 'vercel';\n override readonly credentials = vercelCredentials;\n\n private buildHeaders(): Record<string, string> {\n return {\n Authorization: `Bearer ${this.creds.apiToken}`,\n 'User-Agent': 'rawdash/connector-vercel (+https://rawdash.dev)',\n };\n }\n\n private fetch<T>(\n url: string,\n resource: string,\n signal?: AbortSignal,\n ): Promise<HttpResponse<T>> {\n return this.get<T>(url, {\n resource,\n headers: this.buildHeaders(),\n signal,\n rateLimit: vercelRateLimit,\n });\n }\n\n // -------------------------------------------------------------------------\n // Resource enablement\n // -------------------------------------------------------------------------\n\n private isResourceEnabled(resource: VercelResource): boolean {\n const enabled = this.settings.resources;\n if (!enabled || enabled.length === 0) {\n return true;\n }\n return enabled.includes(resource);\n }\n\n private activePhases(): VercelPhase[] {\n const phases: VercelPhase[] = [];\n if (this.isResourceEnabled('projects')) {\n phases.push('projects');\n }\n if (\n this.isResourceEnabled('deployments') ||\n this.isResourceEnabled('deployment_events')\n ) {\n phases.push('deployments');\n }\n return phases;\n }\n\n // -------------------------------------------------------------------------\n // URL building + sanitization\n // -------------------------------------------------------------------------\n\n private allowedPagePath(phase: VercelPhase): string {\n switch (phase) {\n case 'projects':\n return '/v9/projects';\n case 'deployments':\n return '/v6/deployments';\n }\n }\n\n private sanitizePageUrl(\n phase: VercelPhase,\n pageUrl: string | null,\n ): string | null {\n if (pageUrl === null) {\n return null;\n }\n const allowedPath = this.allowedPagePath(phase);\n try {\n const u = new URL(pageUrl);\n if (\n u.protocol !== 'https:' ||\n u.host !== VERCEL_API_HOST ||\n u.pathname !== allowedPath\n ) {\n return null;\n }\n return u.toString();\n } catch {\n return null;\n }\n }\n\n private resolveCursor(cursor: unknown): VercelSyncCursor | undefined {\n if (!isVercelSyncCursor(cursor)) {\n return undefined;\n }\n return {\n phase: cursor.phase,\n page: this.sanitizePageUrl(cursor.phase, cursor.page),\n };\n }\n\n private withTeamId(u: URL): URL {\n if (this.settings.teamId !== undefined) {\n u.searchParams.set('teamId', this.settings.teamId);\n }\n return u;\n }\n\n private buildInitialProjectsUrl(): string {\n const u = new URL(`${VERCEL_API_BASE}/v9/projects`);\n u.searchParams.set('limit', String(PROJECTS_PAGE_SIZE));\n this.withTeamId(u);\n return u.toString();\n }\n\n private buildInitialDeploymentsUrl(options: SyncOptions): string {\n const u = new URL(`${VERCEL_API_BASE}/v6/deployments`);\n u.searchParams.set('limit', String(DEPLOYMENTS_PAGE_SIZE));\n for (const project of this.settings.projects ?? []) {\n u.searchParams.append('projectId', project);\n }\n const sinceMs = this.computeDeploymentsSinceMs(options);\n if (sinceMs !== null) {\n u.searchParams.set('since', String(sinceMs));\n }\n this.withTeamId(u);\n return u.toString();\n }\n\n private computeDeploymentsSinceMs(options: SyncOptions): number | null {\n if (options.mode === 'latest' && options.since) {\n const ms = new Date(options.since).getTime();\n if (Number.isFinite(ms)) {\n return ms;\n }\n }\n const days = this.settings.deploymentsLookbackDays ?? DEFAULT_LOOKBACK_DAYS;\n return Date.now() - days * MS_PER_DAY;\n }\n\n // -------------------------------------------------------------------------\n // Fetchers\n // -------------------------------------------------------------------------\n\n private buildNextPageUrl(\n phase: VercelPhase,\n currentUrl: string,\n until: number,\n ): string {\n const u = new URL(currentUrl);\n u.searchParams.set('until', String(until));\n return u.toString();\n }\n\n private async fetchProjectsPage(\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<{ items: VercelProject[]; next: string | null }> {\n const url = page ?? this.buildInitialProjectsUrl();\n const res = await this.fetch<VercelProjectsResponse>(\n url,\n 'projects',\n signal,\n );\n const next =\n res.body.pagination.next !== null &&\n res.body.pagination.next !== undefined\n ? this.sanitizePageUrl(\n 'projects',\n this.buildNextPageUrl('projects', url, res.body.pagination.next),\n )\n : null;\n return { items: res.body.projects, next };\n }\n\n private async fetchDeploymentsPage(\n page: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: VercelDeployment[]; next: string | null }> {\n const url = page ?? this.buildInitialDeploymentsUrl(options);\n const res = await this.fetch<VercelDeploymentsResponse>(\n url,\n 'deployments',\n signal,\n );\n const next =\n res.body.pagination.next !== null &&\n res.body.pagination.next !== undefined\n ? this.sanitizePageUrl(\n 'deployments',\n this.buildNextPageUrl('deployments', url, res.body.pagination.next),\n )\n : null;\n return { items: res.body.deployments, next };\n }\n\n // -------------------------------------------------------------------------\n // Writers\n // -------------------------------------------------------------------------\n\n private async writeProjects(\n storage: StorageHandle,\n projects: VercelProject[],\n ): Promise<void> {\n for (const p of projects) {\n const createdMs = Number.isFinite(p.createdAt) ? p.createdAt : null;\n const updatedMs = Number.isFinite(p.updatedAt) ? p.updatedAt : null;\n if (createdMs === null || updatedMs === null) {\n console.warn(\n `[connector-vercel] skipping project ${p.id} with unparseable timestamps`,\n );\n continue;\n }\n await storage.entity({\n type: 'vercel_project',\n id: p.id,\n attributes: {\n name: p.name,\n framework: p.framework,\n accountId: p.accountId ?? null,\n createdAt: createdMs,\n updatedAt: updatedMs,\n },\n updated_at: updatedMs,\n });\n }\n }\n\n private async writeDeployments(\n storage: StorageHandle,\n deployments: VercelDeployment[],\n ): Promise<void> {\n const writeEntities = this.isResourceEnabled('deployments');\n const writeEvents = this.isResourceEnabled('deployment_events');\n\n for (const d of deployments) {\n const createdMs = Number.isFinite(d.created) ? d.created : null;\n if (createdMs === null) {\n console.warn(\n `[connector-vercel] skipping deployment ${d.uid} with unparseable created timestamp`,\n );\n continue;\n }\n const buildingMs =\n d.buildingAt !== null &&\n d.buildingAt !== undefined &&\n Number.isFinite(d.buildingAt)\n ? d.buildingAt\n : null;\n const readyMs =\n d.ready !== null && d.ready !== undefined && Number.isFinite(d.ready)\n ? d.ready\n : null;\n const buildDurationMs =\n readyMs !== null && buildingMs !== null && readyMs >= buildingMs\n ? readyMs - buildingMs\n : null;\n const gitRef =\n d.meta?.githubCommitRef ??\n d.meta?.gitlabCommitRef ??\n d.meta?.bitbucketCommitRef ??\n d.meta?.branch ??\n null;\n const gitSha = d.meta?.githubCommitSha ?? null;\n const projectId = d.projectId ?? null;\n const target = d.target ?? null;\n const creatorUsername = d.creator.username ?? null;\n const baseAttributes: Record<string, JSONValue> = {\n deploymentId: d.uid,\n name: d.name,\n url: d.url,\n state: d.state,\n target,\n projectId,\n creatorUid: d.creator.uid,\n creatorUsername,\n source: d.source ?? null,\n gitRef,\n gitSha,\n createdAt: createdMs,\n buildingAt: buildingMs,\n readyAt: readyMs,\n buildDurationMs,\n };\n\n if (writeEntities) {\n await storage.entity({\n type: 'vercel_deployment',\n id: d.uid,\n attributes: baseAttributes,\n updated_at: readyMs ?? buildingMs ?? createdMs,\n });\n }\n\n if (writeEvents) {\n await storage.event({\n name: 'vercel_deployment_event',\n start_ts: createdMs,\n end_ts: readyMs,\n attributes: baseAttributes,\n });\n }\n }\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 isFull = options.mode === 'full';\n const phases = this.activePhases();\n\n return paginateChunked<VercelPhase, string>({\n phases,\n cursor,\n signal,\n fetchPage: async (phase, page, sig) => {\n switch (phase) {\n case 'projects':\n return this.fetchProjectsPage(page, sig);\n case 'deployments':\n return this.fetchDeploymentsPage(page, options, sig);\n }\n },\n writeBatch: async (phase, items, page) => {\n if (isFull && page === null) {\n switch (phase) {\n case 'projects':\n if (this.isResourceEnabled('projects')) {\n await storage.entities([], { types: ['vercel_project'] });\n }\n break;\n case 'deployments':\n if (this.isResourceEnabled('deployments')) {\n await storage.entities([], { types: ['vercel_deployment'] });\n }\n if (this.isResourceEnabled('deployment_events')) {\n await storage.events([], {\n names: ['vercel_deployment_event'],\n });\n }\n break;\n }\n }\n switch (phase) {\n case 'projects':\n if (!this.isResourceEnabled('projects')) {\n return;\n }\n return this.writeProjects(storage, items as VercelProject[]);\n case 'deployments':\n return this.writeDeployments(storage, items as VercelDeployment[]);\n }\n },\n });\n }\n}\n","import { VercelConnector } from './vercel';\n\nexport { configFields, VercelConnector } from './vercel';\nexport type { VercelResource, VercelSettings } from './vercel';\nexport default VercelConnector;\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rawdash/connector-vercel",
|
|
3
|
+
"version": "0.15.0",
|
|
4
|
+
"description": "Rawdash connector for Vercel — projects, deployments, deploy state transitions",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/rawdash/rawdash.git",
|
|
10
|
+
"directory": "packages/connectors/vercel"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"@rawdash/source": "./src/index.ts",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"lint": "eslint src",
|
|
28
|
+
"test": "vitest run"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@rawdash/core": "workspace:*",
|
|
32
|
+
"zod": "^4.4.3"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@rawdash/connector-shared": "workspace:*",
|
|
36
|
+
"@rawdash/connector-test-utils": "workspace:*",
|
|
37
|
+
"fast-check": "^4.8.0",
|
|
38
|
+
"tsup": "^8.0.0",
|
|
39
|
+
"typescript": "^5.7.2",
|
|
40
|
+
"vitest": "^4.1.4"
|
|
41
|
+
}
|
|
42
|
+
}
|