@pierre/storage 0.0.5 → 0.0.7

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/src/index.ts CHANGED
@@ -5,7 +5,11 @@
5
5
  */
6
6
 
7
7
  import { importPKCS8, SignJWT } from 'jose';
8
+ import snakecaseKeys from 'snakecase-keys';
9
+ import { ApiFetcher } from './fetch';
8
10
  import type {
11
+ CreateRepoOptions,
12
+ FindOneOptions,
9
13
  GetBranchDiffOptions,
10
14
  GetBranchDiffResponse,
11
15
  GetCommitDiffOptions,
@@ -14,60 +18,56 @@ import type {
14
18
  GetCommitResponse,
15
19
  GetFileOptions,
16
20
  GetFileResponse,
21
+ GetRemoteURLOptions,
22
+ GitStorageOptions,
17
23
  ListBranchesOptions,
18
24
  ListBranchesResponse,
19
25
  ListCommitsOptions,
20
26
  ListCommitsResponse,
21
27
  ListFilesOptions,
22
28
  ListFilesResponse,
29
+ OverrideableGitStorageOptions,
23
30
  Repo,
31
+ RepullOptions,
32
+ ValidAPIVersion,
24
33
  } from './types';
25
34
 
26
35
  /**
27
36
  * Type definitions for Pierre Git Storage SDK
28
37
  */
29
38
 
30
- export interface GitStorageOptions {
31
- key: string;
32
- name: string;
33
- }
34
-
35
- export interface GetRemoteURLOptions {
36
- permissions?: ('git:write' | 'git:read' | 'repo:write')[];
37
- ttl?: number;
38
- }
39
-
40
- // Repo interface is now imported from types.ts
41
-
42
- export interface FindOneOptions {
43
- id: string;
44
- }
45
-
46
- export interface CreateRepoOptions {
47
- id?: string;
48
- }
49
-
50
- export interface CreateRepoResponse {
51
- repo_id: string;
52
- url: string;
53
- }
54
-
55
39
  // Import additional types from types.ts
56
40
  export * from './types';
57
41
 
42
+ // Export webhook validation utilities
43
+ export { parseSignatureHeader, validateWebhook, validateWebhookSignature } from './webhook';
44
+
58
45
  /**
59
46
  * Git Storage API
60
47
  */
61
- declare const __API_BASE_URL__: string;
48
+
62
49
  declare const __STORAGE_BASE_URL__: string;
50
+ declare const __API_BASE_URL__: string;
63
51
 
64
52
  const API_BASE_URL = __API_BASE_URL__;
65
53
  const STORAGE_BASE_URL = __STORAGE_BASE_URL__;
54
+ const API_VERSION: ValidAPIVersion = 1;
55
+
56
+ const apiInstanceMap = new Map<string, ApiFetcher>();
57
+
58
+ function getApiInstance(baseUrl: string, version: ValidAPIVersion) {
59
+ if (!apiInstanceMap.has(`${baseUrl}--${version}`)) {
60
+ apiInstanceMap.set(`${baseUrl}--${version}`, new ApiFetcher(baseUrl, version));
61
+ }
62
+ return apiInstanceMap.get(`${baseUrl}--${version}`)!;
63
+ }
66
64
 
67
65
  /**
68
66
  * Implementation of the Repo interface
69
67
  */
70
68
  class RepoImpl implements Repo {
69
+ private readonly api: ApiFetcher;
70
+
71
71
  constructor(
72
72
  public readonly id: string,
73
73
  private readonly options: GitStorageOptions,
@@ -75,10 +75,16 @@ class RepoImpl implements Repo {
75
75
  repoId: string,
76
76
  options?: GetRemoteURLOptions,
77
77
  ) => Promise<string>,
78
- ) {}
78
+ ) {
79
+ this.api = getApiInstance(
80
+ this.options.apiBaseUrl ?? API_BASE_URL,
81
+ this.options.apiVersion ?? API_VERSION,
82
+ );
83
+ }
79
84
 
80
85
  async getRemoteURL(urlOptions?: GetRemoteURLOptions): Promise<string> {
81
- const url = new URL(`https://${this.options.name}.${STORAGE_BASE_URL}/${this.id}.git`);
86
+ const storageBaseUrl = this.options.storageBaseUrl ?? STORAGE_BASE_URL;
87
+ const url = new URL(`https://${this.options.name}.${storageBaseUrl}/${this.id}.git`);
82
88
  url.username = `t`;
83
89
  url.password = await this.generateJWT(this.id, urlOptions);
84
90
  return url.toString();
@@ -87,25 +93,18 @@ class RepoImpl implements Repo {
87
93
  async getFile(options: GetFileOptions): Promise<GetFileResponse> {
88
94
  const jwt = await this.generateJWT(this.id, {
89
95
  permissions: ['git:read'],
90
- ttl: 1 * 60 * 60, // 1hr in seconds
96
+ ttl: options?.ttl ?? 1 * 60 * 60, // 1hr in seconds
91
97
  });
92
98
 
93
- const url = new URL(`${API_BASE_URL}/api/v1/repos/file`);
94
- url.searchParams.set('path', options.path);
99
+ const params: Record<string, string> = {
100
+ path: options.path,
101
+ };
102
+
95
103
  if (options.ref) {
96
- url.searchParams.set('ref', options.ref);
104
+ params.ref = options.ref;
97
105
  }
98
106
 
99
- const response = await fetch(url.toString(), {
100
- method: 'GET',
101
- headers: {
102
- Authorization: `Bearer ${jwt}`,
103
- },
104
- });
105
-
106
- if (!response.ok) {
107
- throw new Error(`Failed to get file: ${response.statusText}`);
108
- }
107
+ const response = await this.api.get({ path: 'repos/file', params }, jwt);
109
108
 
110
109
  return (await response.json()) as GetFileResponse;
111
110
  }
@@ -113,24 +112,13 @@ class RepoImpl implements Repo {
113
112
  async listFiles(options?: ListFilesOptions): Promise<ListFilesResponse> {
114
113
  const jwt = await this.generateJWT(this.id, {
115
114
  permissions: ['git:read'],
116
- ttl: 1 * 60 * 60, // 1hr in seconds
117
- });
118
-
119
- const url = new URL(`${API_BASE_URL}/api/v1/repos/files`);
120
- if (options?.ref) {
121
- url.searchParams.set('ref', options.ref);
122
- }
123
-
124
- const response = await fetch(url.toString(), {
125
- method: 'GET',
126
- headers: {
127
- Authorization: `Bearer ${jwt}`,
128
- },
115
+ ttl: options?.ttl ?? 1 * 60 * 60, // 1hr in seconds
129
116
  });
130
117
 
131
- if (!response.ok) {
132
- throw new Error(`Failed to list files: ${response.statusText}`);
133
- }
118
+ const params: Record<string, string> | undefined = options?.ref
119
+ ? { ref: options.ref }
120
+ : undefined;
121
+ const response = await this.api.get({ path: 'repos/files', params }, jwt);
134
122
 
135
123
  return (await response.json()) as ListFilesResponse;
136
124
  }
@@ -138,58 +126,48 @@ class RepoImpl implements Repo {
138
126
  async listBranches(options?: ListBranchesOptions): Promise<ListBranchesResponse> {
139
127
  const jwt = await this.generateJWT(this.id, {
140
128
  permissions: ['git:read'],
141
- ttl: 1 * 60 * 60, // 1hr in seconds
129
+ ttl: options?.ttl ?? 1 * 60 * 60, // 1hr in seconds
142
130
  });
143
131
 
144
- const url = new URL(`${API_BASE_URL}/api/v1/repos/branches`);
145
- if (options?.cursor) {
146
- url.searchParams.set('cursor', options.cursor);
147
- }
148
- if (options?.limit) {
149
- url.searchParams.set('limit', options.limit.toString());
150
- }
151
-
152
- const response = await fetch(url.toString(), {
153
- method: 'GET',
154
- headers: {
155
- Authorization: `Bearer ${jwt}`,
156
- },
157
- });
132
+ let params: Record<string, string> | undefined;
158
133
 
159
- if (!response.ok) {
160
- throw new Error(`Failed to list branches: ${response.statusText}`);
134
+ if (options?.cursor || !options?.limit) {
135
+ params = {};
136
+ if (options?.cursor) {
137
+ params.cursor = options.cursor;
138
+ }
139
+ if (typeof options?.limit == 'number') {
140
+ params.limit = options.limit.toString();
141
+ }
161
142
  }
162
143
 
144
+ const response = await this.api.get({ path: 'repos/branches', params }, jwt);
145
+
163
146
  return (await response.json()) as ListBranchesResponse;
164
147
  }
165
148
 
166
149
  async listCommits(options?: ListCommitsOptions): Promise<ListCommitsResponse> {
167
150
  const jwt = await this.generateJWT(this.id, {
168
151
  permissions: ['git:read'],
169
- ttl: 1 * 60 * 60, // 1hr in seconds
152
+ ttl: options?.ttl ?? 1 * 60 * 60, // 1hr in seconds
170
153
  });
171
154
 
172
- const url = new URL(`${API_BASE_URL}/api/v1/repos/commits`);
173
- if (options?.branch) {
174
- url.searchParams.set('branch', options.branch);
175
- }
176
- if (options?.cursor) {
177
- url.searchParams.set('cursor', options.cursor);
178
- }
179
- if (options?.limit) {
180
- url.searchParams.set('limit', options.limit.toString());
155
+ let params: Record<string, string> | undefined;
156
+
157
+ if (options?.branch || options?.cursor || options?.limit) {
158
+ params = {};
159
+ if (options?.branch) {
160
+ params.branch = options.branch;
161
+ }
162
+ if (options?.cursor) {
163
+ params.cursor = options.cursor;
164
+ }
165
+ if (typeof options?.limit == 'number') {
166
+ params.limit = options.limit.toString();
167
+ }
181
168
  }
182
169
 
183
- const response = await fetch(url.toString(), {
184
- method: 'GET',
185
- headers: {
186
- Authorization: `Bearer ${jwt}`,
187
- },
188
- });
189
-
190
- if (!response.ok) {
191
- throw new Error(`Failed to list commits: ${response.statusText}`);
192
- }
170
+ const response = await this.api.get({ path: 'repos/commits', params }, jwt);
193
171
 
194
172
  return (await response.json()) as ListCommitsResponse;
195
173
  }
@@ -197,25 +175,18 @@ class RepoImpl implements Repo {
197
175
  async getBranchDiff(options: GetBranchDiffOptions): Promise<GetBranchDiffResponse> {
198
176
  const jwt = await this.generateJWT(this.id, {
199
177
  permissions: ['git:read'],
200
- ttl: 1 * 60 * 60, // 1hr in seconds
178
+ ttl: options?.ttl ?? 1 * 60 * 60, // 1hr in seconds
201
179
  });
202
180
 
203
- const url = new URL(`${API_BASE_URL}/api/v1/repos/branches/diff`);
204
- url.searchParams.set('branch', options.branch);
181
+ const params: Record<string, string> = {
182
+ branch: options.branch,
183
+ };
184
+
205
185
  if (options.base) {
206
- url.searchParams.set('base', options.base);
186
+ params.base = options.base;
207
187
  }
208
188
 
209
- const response = await fetch(url.toString(), {
210
- method: 'GET',
211
- headers: {
212
- Authorization: `Bearer ${jwt}`,
213
- },
214
- });
215
-
216
- if (!response.ok) {
217
- throw new Error(`Failed to get branch diff: ${response.statusText}`);
218
- }
189
+ const response = await this.api.get({ path: 'repos/branches/diff', params }, jwt);
219
190
 
220
191
  return (await response.json()) as GetBranchDiffResponse;
221
192
  }
@@ -223,22 +194,14 @@ class RepoImpl implements Repo {
223
194
  async getCommitDiff(options: GetCommitDiffOptions): Promise<GetCommitDiffResponse> {
224
195
  const jwt = await this.generateJWT(this.id, {
225
196
  permissions: ['git:read'],
226
- ttl: 1 * 60 * 60, // 1hr in seconds
197
+ ttl: options?.ttl ?? 1 * 60 * 60, // 1hr in seconds
227
198
  });
228
199
 
229
- const url = new URL(`${API_BASE_URL}/api/v1/repos/diff`);
230
- url.searchParams.set('sha', options.sha);
231
-
232
- const response = await fetch(url.toString(), {
233
- method: 'GET',
234
- headers: {
235
- Authorization: `Bearer ${jwt}`,
236
- },
237
- });
200
+ const params: Record<string, string> = {
201
+ sha: options.sha,
202
+ };
238
203
 
239
- if (!response.ok) {
240
- throw new Error(`Failed to get commit diff: ${response.statusText}`);
241
- }
204
+ const response = await this.api.get({ path: 'repos/diff', params }, jwt);
242
205
 
243
206
  return (await response.json()) as GetCommitDiffResponse;
244
207
  }
@@ -246,30 +209,44 @@ class RepoImpl implements Repo {
246
209
  async getCommit(options: GetCommitOptions): Promise<GetCommitResponse> {
247
210
  const jwt = await this.generateJWT(this.id, {
248
211
  permissions: ['git:read'],
249
- ttl: 1 * 60 * 60, // 1hr in seconds
212
+ ttl: options?.ttl ?? 1 * 60 * 60, // 1hr in seconds
250
213
  });
251
214
 
252
- const url = new URL(`${API_BASE_URL}/commit`);
253
- url.searchParams.set('repo', this.id);
254
- url.searchParams.set('sha', options.sha);
215
+ const params: Record<string, string> = {
216
+ repo: this.id,
217
+ sha: options.sha,
218
+ };
219
+ const response = await this.api.get({ path: 'commit', params }, jwt);
220
+
221
+ return (await response.json()) as GetCommitResponse;
222
+ }
255
223
 
256
- const response = await fetch(url.toString(), {
257
- method: 'GET',
258
- headers: {
259
- Authorization: `Bearer ${jwt}`,
260
- },
224
+ async repull(options: RepullOptions): Promise<void> {
225
+ const jwt = await this.generateJWT(this.id, {
226
+ permissions: ['git:write'],
227
+ ttl: options?.ttl ?? 1 * 60 * 60, // 1hr in seconds
261
228
  });
262
229
 
263
- if (!response.ok) {
264
- throw new Error(`Failed to get commit: ${response.statusText}`);
230
+ const body: Record<string, string> = {};
231
+
232
+ if (options.ref) {
233
+ body.ref = options.ref;
265
234
  }
266
235
 
267
- return (await response.json()) as GetCommitResponse;
236
+ const response = await this.api.post({ path: 'repos/repull', body }, jwt);
237
+
238
+ if (response.status !== 202) {
239
+ throw new Error(`Repull failed: ${response.status} ${await response.text()}`);
240
+ }
241
+
242
+ return;
268
243
  }
269
244
  }
270
245
 
271
246
  export class GitStorage {
247
+ private static overrides: OverrideableGitStorageOptions = {};
272
248
  private options: GitStorageOptions;
249
+ private api: ApiFetcher;
273
250
 
274
251
  constructor(options: GitStorageOptions) {
275
252
  if (
@@ -292,12 +269,27 @@ export class GitStorage {
292
269
  throw new Error('GitStorage key must be a non-empty string.');
293
270
  }
294
271
 
272
+ const resolvedApiBaseUrl =
273
+ options.apiBaseUrl ?? GitStorage.overrides.apiBaseUrl ?? API_BASE_URL;
274
+ const resolvedApiVersion = options.apiVersion ?? GitStorage.overrides.apiVersion ?? API_VERSION;
275
+ const resolvedStorageBaseUrl =
276
+ options.storageBaseUrl ?? GitStorage.overrides.storageBaseUrl ?? STORAGE_BASE_URL;
277
+
278
+ this.api = getApiInstance(resolvedApiBaseUrl, resolvedApiVersion);
279
+
295
280
  this.options = {
296
281
  key: options.key,
297
282
  name: options.name,
283
+ apiBaseUrl: resolvedApiBaseUrl,
284
+ apiVersion: resolvedApiVersion,
285
+ storageBaseUrl: resolvedStorageBaseUrl,
298
286
  };
299
287
  }
300
288
 
289
+ static override(options: OverrideableGitStorageOptions): void {
290
+ this.overrides = Object.assign({}, this.overrides, options);
291
+ }
292
+
301
293
  /**
302
294
  * Create a new repository
303
295
  * @returns The created repository
@@ -307,18 +299,34 @@ export class GitStorage {
307
299
 
308
300
  const jwt = await this.generateJWT(repoId, {
309
301
  permissions: ['repo:write'],
310
- ttl: 1 * 60 * 60, // 1hr in seconds
311
- });
312
-
313
- const response = await fetch(`${API_BASE_URL}/api/v1/repos`, {
314
- method: 'POST',
315
- headers: {
316
- Authorization: `Bearer ${jwt}`,
317
- },
302
+ ttl: options?.ttl ?? 1 * 60 * 60, // 1hr in seconds
318
303
  });
319
304
 
320
- if (!response.ok) {
321
- throw new Error(`Failed to create repository: ${response.statusText}`);
305
+ const baseRepoOptions = options?.baseRepo
306
+ ? {
307
+ provider: 'github',
308
+ ...snakecaseKeys(options.baseRepo as unknown as Record<string, unknown>),
309
+ }
310
+ : null;
311
+
312
+ // Default defaultBranch to 'main' if not provided
313
+ const defaultBranch = options?.defaultBranch ?? 'main';
314
+
315
+ const createRepoPath =
316
+ baseRepoOptions || defaultBranch
317
+ ? {
318
+ path: 'repos',
319
+ body: {
320
+ ...(baseRepoOptions && { base_repo: baseRepoOptions }),
321
+ default_branch: defaultBranch,
322
+ },
323
+ }
324
+ : 'repos';
325
+
326
+ // Allow 409 so we can map it to a clearer error message
327
+ const resp = await this.api.post(createRepoPath, jwt, { allowedStatus: [409] });
328
+ if (resp.status === 409) {
329
+ throw new Error('Repository already exists');
322
330
  }
323
331
 
324
332
  return new RepoImpl(repoId, this.options, this.generateJWT.bind(this));
@@ -330,6 +338,18 @@ export class GitStorage {
330
338
  * @returns The found repository
331
339
  */
332
340
  async findOne(options: FindOneOptions): Promise<Repo | null> {
341
+ const jwt = await this.generateJWT(options.id, {
342
+ permissions: ['git:read'],
343
+ ttl: 1 * 60 * 60,
344
+ });
345
+
346
+ // Allow 404 to indicate "not found" without throwing
347
+ const resp = await this.api.get('repo', jwt, { allowedStatus: [404] });
348
+ if (resp.status === 404) {
349
+ return null;
350
+ }
351
+ // On 200, we could validate response, but RepoImpl only needs the repo URL/id
352
+ // const body = await resp.json(); // not required for now
333
353
  return new RepoImpl(options.id, this.options, this.generateJWT.bind(this));
334
354
  }
335
355
 
package/src/types.ts CHANGED
@@ -2,11 +2,20 @@
2
2
  * Type definitions for Pierre Git Storage SDK
3
3
  */
4
4
 
5
- export interface GitStorageOptions {
5
+ export interface OverrideableGitStorageOptions {
6
+ apiBaseUrl?: string;
7
+ storageBaseUrl?: string;
8
+ apiVersion?: ValidAPIVersion;
9
+ }
10
+
11
+ export interface GitStorageOptions extends OverrideableGitStorageOptions {
6
12
  key: string;
7
13
  name: string;
14
+ defaultTTL?: number;
8
15
  }
9
16
 
17
+ export type ValidAPIVersion = 1;
18
+
10
19
  export interface GetRemoteURLOptions {
11
20
  permissions?: ('git:write' | 'git:read' | 'repo:write')[];
12
21
  ttl?: number;
@@ -24,12 +33,39 @@ export interface Repo {
24
33
  getCommit(options: GetCommitOptions): Promise<GetCommitResponse>;
25
34
  }
26
35
 
36
+ export type ValidMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
37
+ type SimplePath = string;
38
+ type ComplexPath = {
39
+ path: string;
40
+ params?: Record<string, string>;
41
+ body?: Record<string, any>;
42
+ };
43
+ export type ValidPath = SimplePath | ComplexPath;
44
+
45
+ interface GitStorageInvocationOptions {
46
+ ttl?: number;
47
+ }
48
+
27
49
  export interface FindOneOptions {
28
50
  id: string;
29
51
  }
30
52
 
31
- export interface CreateRepoOptions {
53
+ export type SupportedRepoProvider = 'github';
54
+
55
+ export interface BaseRepo {
56
+ /**
57
+ * @default github
58
+ */
59
+ provider?: SupportedRepoProvider;
60
+ owner: string;
61
+ name: string;
62
+ defaultBranch?: string;
63
+ }
64
+
65
+ export interface CreateRepoOptions extends GitStorageInvocationOptions {
32
66
  id?: string;
67
+ baseRepo?: BaseRepo;
68
+ defaultBranch?: string;
33
69
  }
34
70
 
35
71
  export interface CreateRepoResponse {
@@ -38,7 +74,7 @@ export interface CreateRepoResponse {
38
74
  }
39
75
 
40
76
  // Get File API types
41
- export interface GetFileOptions {
77
+ export interface GetFileOptions extends GitStorageInvocationOptions {
42
78
  path: string;
43
79
  ref?: string;
44
80
  }
@@ -51,8 +87,12 @@ export interface GetFileResponse {
51
87
  is_binary: boolean;
52
88
  }
53
89
 
90
+ export interface RepullOptions extends GitStorageInvocationOptions {
91
+ ref?: string;
92
+ }
93
+
54
94
  // List Files API types
55
- export interface ListFilesOptions {
95
+ export interface ListFilesOptions extends GitStorageInvocationOptions {
56
96
  ref?: string;
57
97
  }
58
98
 
@@ -62,7 +102,7 @@ export interface ListFilesResponse {
62
102
  }
63
103
 
64
104
  // List Branches API types
65
- export interface ListBranchesOptions {
105
+ export interface ListBranchesOptions extends GitStorageInvocationOptions {
66
106
  cursor?: string;
67
107
  limit?: number;
68
108
  }
@@ -81,7 +121,7 @@ export interface ListBranchesResponse {
81
121
  }
82
122
 
83
123
  // List Commits API types
84
- export interface ListCommitsOptions {
124
+ export interface ListCommitsOptions extends GitStorageInvocationOptions {
85
125
  branch?: string;
86
126
  cursor?: string;
87
127
  limit?: number;
@@ -104,7 +144,7 @@ export interface ListCommitsResponse {
104
144
  }
105
145
 
106
146
  // Branch Diff API types
107
- export interface GetBranchDiffOptions {
147
+ export interface GetBranchDiffOptions extends GitStorageInvocationOptions {
108
148
  branch: string;
109
149
  base?: string;
110
150
  }
@@ -118,7 +158,7 @@ export interface GetBranchDiffResponse {
118
158
  }
119
159
 
120
160
  // Commit Diff API types
121
- export interface GetCommitDiffOptions {
161
+ export interface GetCommitDiffOptions extends GitStorageInvocationOptions {
122
162
  sha: string;
123
163
  }
124
164
 
@@ -155,7 +195,7 @@ export interface FilteredFile {
155
195
  }
156
196
 
157
197
  // Get Commit API types
158
- export interface GetCommitOptions {
198
+ export interface GetCommitOptions extends GitStorageInvocationOptions {
159
199
  sha: string;
160
200
  }
161
201
 
@@ -177,3 +217,51 @@ export interface GetCommitResponse {
177
217
  total: number;
178
218
  };
179
219
  }
220
+
221
+ // Webhook types
222
+ export interface WebhookValidationOptions {
223
+ /**
224
+ * Maximum age of webhook in seconds (default: 300 seconds / 5 minutes)
225
+ * Set to 0 to disable timestamp validation
226
+ */
227
+ maxAgeSeconds?: number;
228
+ }
229
+
230
+ export interface WebhookValidationResult {
231
+ /**
232
+ * Whether the webhook signature and timestamp are valid
233
+ */
234
+ valid: boolean;
235
+ /**
236
+ * Error message if validation failed
237
+ */
238
+ error?: string;
239
+ /**
240
+ * The parsed webhook event type (e.g., "push")
241
+ */
242
+ eventType?: string;
243
+ /**
244
+ * The timestamp from the signature (Unix seconds)
245
+ */
246
+ timestamp?: number;
247
+ }
248
+
249
+ // Webhook event payloads
250
+ export interface WebhookPushEvent {
251
+ repository: {
252
+ id: string;
253
+ url: string;
254
+ };
255
+ ref: string;
256
+ before: string;
257
+ after: string;
258
+ customer_id: string;
259
+ pushed_at: string; // RFC3339 timestamp
260
+ }
261
+
262
+ export type WebhookEventPayload = WebhookPushEvent;
263
+
264
+ export interface ParsedWebhookSignature {
265
+ timestamp: string;
266
+ signature: string;
267
+ }