@pierre/storage 0.0.3 → 0.0.6

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 CHANGED
@@ -68,6 +68,59 @@ const readOnlyUrl = await repo.getRemoteURL({
68
68
  // - 'repo:write' - Create a repository
69
69
  ```
70
70
 
71
+ ### Working with Repository Content
72
+
73
+ Once you have a repository instance, you can perform various Git operations:
74
+
75
+ ```typescript
76
+ const repo = await store.createRepo();
77
+ // or
78
+ const repo = await store.findOne({ id: 'existing-repo-id' });
79
+
80
+ // Get file content
81
+ const file = await repo.getFile({
82
+ path: 'README.md',
83
+ ref: 'main', // optional, defaults to default branch
84
+ });
85
+ console.log(file.content);
86
+
87
+ // List all files in the repository
88
+ const files = await repo.listFiles({
89
+ ref: 'main', // optional, defaults to default branch
90
+ });
91
+ console.log(files.paths); // Array of file paths
92
+
93
+ // List branches
94
+ const branches = await repo.listBranches({
95
+ limit: 10,
96
+ cursor: undefined, // for pagination
97
+ });
98
+ console.log(branches.branches);
99
+
100
+ // List commits
101
+ const commits = await repo.listCommits({
102
+ branch: 'main', // optional
103
+ limit: 20,
104
+ cursor: undefined, // for pagination
105
+ });
106
+ console.log(commits.commits);
107
+
108
+ // Get branch diff
109
+ const branchDiff = await repo.getBranchDiff({
110
+ branch: 'feature-branch',
111
+ base: 'main', // optional, defaults to main
112
+ });
113
+ console.log(branchDiff.stats);
114
+ console.log(branchDiff.files);
115
+
116
+ // Get commit diff
117
+ const commitDiff = await repo.getCommitDiff({
118
+ sha: 'abc123...',
119
+ });
120
+ console.log(commitDiff.stats);
121
+ console.log(commitDiff.files);
122
+ ```
123
+
71
124
  ## API Reference
72
125
 
73
126
  ### GitStorage
@@ -100,12 +153,129 @@ interface FindOneOptions {
100
153
  interface Repo {
101
154
  id: string;
102
155
  getRemoteURL(options?: GetRemoteURLOptions): Promise<string>;
156
+ getFile(options: GetFileOptions): Promise<GetFileResponse>;
157
+ listFiles(options?: ListFilesOptions): Promise<ListFilesResponse>;
158
+ listBranches(options?: ListBranchesOptions): Promise<ListBranchesResponse>;
159
+ listCommits(options?: ListCommitsOptions): Promise<ListCommitsResponse>;
160
+ getBranchDiff(options: GetBranchDiffOptions): Promise<GetBranchDiffResponse>;
161
+ getCommitDiff(options: GetCommitDiffOptions): Promise<GetCommitDiffResponse>;
103
162
  }
104
163
 
105
164
  interface GetRemoteURLOptions {
106
165
  permissions?: ('git:write' | 'git:read' | 'repo:write')[];
107
166
  ttl?: number; // Time to live in seconds (default: 31536000 = 1 year)
108
167
  }
168
+
169
+ // Git operation interfaces
170
+ interface GetFileOptions {
171
+ path: string;
172
+ ref?: string; // Branch, tag, or commit SHA
173
+ }
174
+
175
+ interface GetFileResponse {
176
+ path: string;
177
+ ref: string;
178
+ content: string;
179
+ size: number;
180
+ is_binary: boolean;
181
+ }
182
+
183
+ interface ListFilesOptions {
184
+ ref?: string; // Branch, tag, or commit SHA
185
+ }
186
+
187
+ interface ListFilesResponse {
188
+ paths: string[]; // Array of file paths
189
+ ref: string; // The resolved reference
190
+ }
191
+
192
+ interface ListBranchesOptions {
193
+ cursor?: string;
194
+ limit?: number;
195
+ }
196
+
197
+ interface ListBranchesResponse {
198
+ branches: BranchInfo[];
199
+ next_cursor?: string;
200
+ has_more: boolean;
201
+ }
202
+
203
+ interface BranchInfo {
204
+ cursor: string;
205
+ name: string;
206
+ head_sha: string;
207
+ created_at: string;
208
+ }
209
+
210
+ interface ListCommitsOptions {
211
+ branch?: string;
212
+ cursor?: string;
213
+ limit?: number;
214
+ }
215
+
216
+ interface ListCommitsResponse {
217
+ commits: CommitInfo[];
218
+ next_cursor?: string;
219
+ has_more: boolean;
220
+ }
221
+
222
+ interface CommitInfo {
223
+ sha: string;
224
+ message: string;
225
+ author_name: string;
226
+ author_email: string;
227
+ committer_name: string;
228
+ committer_email: string;
229
+ date: string;
230
+ }
231
+
232
+ interface GetBranchDiffOptions {
233
+ branch: string;
234
+ base?: string; // Defaults to 'main'
235
+ }
236
+
237
+ interface GetCommitDiffOptions {
238
+ sha: string;
239
+ }
240
+
241
+ interface GetBranchDiffResponse {
242
+ branch: string;
243
+ base: string;
244
+ stats: DiffStats;
245
+ files: FileDiff[];
246
+ filtered_files: FilteredFile[];
247
+ }
248
+
249
+ interface GetCommitDiffResponse {
250
+ sha: string;
251
+ stats: DiffStats;
252
+ files: FileDiff[];
253
+ filtered_files: FilteredFile[];
254
+ }
255
+
256
+ interface DiffStats {
257
+ files: number;
258
+ additions: number;
259
+ deletions: number;
260
+ changes: number;
261
+ }
262
+
263
+ interface FileDiff {
264
+ path: string;
265
+ state: string;
266
+ old_path?: string;
267
+ bytes: number;
268
+ is_eof: boolean;
269
+ diff: string;
270
+ }
271
+
272
+ interface FilteredFile {
273
+ path: string;
274
+ state: string;
275
+ old_path?: string;
276
+ bytes: number;
277
+ is_eof: boolean;
278
+ }
109
279
  ```
110
280
 
111
281
  ## Authentication
package/dist/index.cjs CHANGED
@@ -1,12 +1,398 @@
1
1
  'use strict';
2
2
 
3
3
  var jose = require('jose');
4
+ var snakecaseKeys = require('snakecase-keys');
5
+
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ var snakecaseKeys__default = /*#__PURE__*/_interopDefault(snakecaseKeys);
9
+
10
+ // src/index.ts
11
+
12
+ // src/fetch.ts
13
+ var ApiFetcher = class {
14
+ constructor(API_BASE_URL2, version) {
15
+ this.API_BASE_URL = API_BASE_URL2;
16
+ this.version = version;
17
+ console.log("api fetcher created", API_BASE_URL2, version);
18
+ }
19
+ getBaseUrl() {
20
+ return `${this.API_BASE_URL}/api/v${this.version}`;
21
+ }
22
+ getRequestUrl(path) {
23
+ if (typeof path === "string") {
24
+ return `${this.getBaseUrl()}/${path}`;
25
+ } else if (path.params) {
26
+ const paramStr = new URLSearchParams(path.params).toString();
27
+ return `${this.getBaseUrl()}/${path.path}${paramStr ? `?${paramStr}` : ""}`;
28
+ } else {
29
+ return `${this.getBaseUrl()}/${path.path}`;
30
+ }
31
+ }
32
+ async fetch(path, method, jwt, options) {
33
+ const requestUrl = this.getRequestUrl(path);
34
+ const requestOptions = {
35
+ method,
36
+ headers: {
37
+ Authorization: `Bearer ${jwt}`,
38
+ "Content-Type": "application/json"
39
+ }
40
+ };
41
+ if (method !== "GET" && typeof path !== "string" && path.body) {
42
+ requestOptions.body = JSON.stringify(path.body);
43
+ }
44
+ const response = await fetch(requestUrl, requestOptions);
45
+ if (!response.ok) {
46
+ const allowed = options?.allowedStatus ?? [];
47
+ if (!allowed.includes(response.status)) {
48
+ throw new Error(`Failed to fetch ${method} ${requestUrl}: ${response.statusText}`);
49
+ }
50
+ }
51
+ return response;
52
+ }
53
+ async get(path, jwt, options) {
54
+ return this.fetch(path, "GET", jwt, options);
55
+ }
56
+ async post(path, jwt, options) {
57
+ return this.fetch(path, "POST", jwt, options);
58
+ }
59
+ async put(path, jwt, options) {
60
+ return this.fetch(path, "PUT", jwt, options);
61
+ }
62
+ async delete(path, jwt, options) {
63
+ return this.fetch(path, "DELETE", jwt, options);
64
+ }
65
+ };
66
+
67
+ // src/util.ts
68
+ function timingSafeEqual(a, b) {
69
+ const bufferA = typeof a === "string" ? new TextEncoder().encode(a) : a;
70
+ const bufferB = typeof b === "string" ? new TextEncoder().encode(b) : b;
71
+ if (bufferA.length !== bufferB.length) return false;
72
+ let result = 0;
73
+ for (let i = 0; i < bufferA.length; i++) {
74
+ result |= bufferA[i] ^ bufferB[i];
75
+ }
76
+ return result === 0;
77
+ }
78
+ async function getEnvironmentCrypto() {
79
+ if (!globalThis.crypto) {
80
+ const { webcrypto } = await import('crypto');
81
+ return webcrypto;
82
+ }
83
+ return globalThis.crypto;
84
+ }
85
+ async function createHmac(algorithm, secret, data) {
86
+ if (!secret || secret.length === 0) {
87
+ throw new Error("Secret is required");
88
+ }
89
+ const crypto2 = await getEnvironmentCrypto();
90
+ const encoder = new TextEncoder();
91
+ const key = await crypto2.subtle.importKey(
92
+ "raw",
93
+ encoder.encode(secret),
94
+ { name: "HMAC", hash: "SHA-256" },
95
+ false,
96
+ ["sign"]
97
+ );
98
+ const signature = await crypto2.subtle.sign("HMAC", key, encoder.encode(data));
99
+ return Array.from(new Uint8Array(signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
100
+ }
101
+
102
+ // src/webhook.ts
103
+ var DEFAULT_MAX_AGE_SECONDS = 300;
104
+ function parseSignatureHeader(header) {
105
+ if (!header || typeof header !== "string") {
106
+ return null;
107
+ }
108
+ let timestamp = "";
109
+ let signature = "";
110
+ const elements = header.split(",");
111
+ for (const element of elements) {
112
+ const trimmedElement = element.trim();
113
+ const parts = trimmedElement.split("=", 2);
114
+ if (parts.length !== 2) {
115
+ continue;
116
+ }
117
+ const [key, value] = parts;
118
+ switch (key) {
119
+ case "t":
120
+ timestamp = value;
121
+ break;
122
+ case "sha256":
123
+ signature = value;
124
+ break;
125
+ }
126
+ }
127
+ if (!timestamp || !signature) {
128
+ return null;
129
+ }
130
+ return { timestamp, signature };
131
+ }
132
+ async function validateWebhookSignature(payload, signatureHeader, secret, options = {}) {
133
+ if (!secret || secret.length === 0) {
134
+ return {
135
+ valid: false,
136
+ error: "Empty secret is not allowed"
137
+ };
138
+ }
139
+ const parsed = parseSignatureHeader(signatureHeader);
140
+ if (!parsed) {
141
+ return {
142
+ valid: false,
143
+ error: "Invalid signature header format"
144
+ };
145
+ }
146
+ const timestamp = Number.parseInt(parsed.timestamp, 10);
147
+ if (isNaN(timestamp)) {
148
+ return {
149
+ valid: false,
150
+ error: "Invalid timestamp in signature"
151
+ };
152
+ }
153
+ const maxAge = options.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS;
154
+ if (maxAge > 0) {
155
+ const now = Math.floor(Date.now() / 1e3);
156
+ const age = now - timestamp;
157
+ if (age > maxAge) {
158
+ return {
159
+ valid: false,
160
+ error: `Webhook timestamp too old (${age} seconds)`,
161
+ timestamp
162
+ };
163
+ }
164
+ if (age < -60) {
165
+ return {
166
+ valid: false,
167
+ error: "Webhook timestamp is in the future",
168
+ timestamp
169
+ };
170
+ }
171
+ }
172
+ const payloadStr = typeof payload === "string" ? payload : payload.toString("utf8");
173
+ const signedData = `${parsed.timestamp}.${payloadStr}`;
174
+ const expectedSignature = await createHmac("sha256", secret, signedData);
175
+ const expectedBuffer = Buffer.from(expectedSignature);
176
+ const actualBuffer = Buffer.from(parsed.signature);
177
+ if (expectedBuffer.length !== actualBuffer.length) {
178
+ return {
179
+ valid: false,
180
+ error: "Invalid signature",
181
+ timestamp
182
+ };
183
+ }
184
+ const signaturesMatch = timingSafeEqual(expectedBuffer, actualBuffer);
185
+ if (!signaturesMatch) {
186
+ return {
187
+ valid: false,
188
+ error: "Invalid signature",
189
+ timestamp
190
+ };
191
+ }
192
+ return {
193
+ valid: true,
194
+ timestamp
195
+ };
196
+ }
197
+ async function validateWebhook(payload, headers, secret, options = {}) {
198
+ const signatureHeader = headers["x-pierre-signature"] || headers["X-Pierre-Signature"];
199
+ if (!signatureHeader || Array.isArray(signatureHeader)) {
200
+ return {
201
+ valid: false,
202
+ error: "Missing or invalid X-Pierre-Signature header"
203
+ };
204
+ }
205
+ const eventType = headers["x-pierre-event"] || headers["X-Pierre-Event"];
206
+ if (!eventType || Array.isArray(eventType)) {
207
+ return {
208
+ valid: false,
209
+ error: "Missing or invalid X-Pierre-Event header"
210
+ };
211
+ }
212
+ const validationResult = await validateWebhookSignature(
213
+ payload,
214
+ signatureHeader,
215
+ secret,
216
+ options
217
+ );
218
+ if (!validationResult.valid) {
219
+ return validationResult;
220
+ }
221
+ const payloadStr = typeof payload === "string" ? payload : payload.toString("utf8");
222
+ let parsedPayload;
223
+ try {
224
+ parsedPayload = JSON.parse(payloadStr);
225
+ } catch {
226
+ return {
227
+ valid: false,
228
+ error: "Invalid JSON payload",
229
+ timestamp: validationResult.timestamp
230
+ };
231
+ }
232
+ return {
233
+ valid: true,
234
+ eventType,
235
+ timestamp: validationResult.timestamp,
236
+ payload: parsedPayload
237
+ };
238
+ }
4
239
 
5
240
  // src/index.ts
6
241
  var API_BASE_URL = "https://api.git.storage";
7
242
  var STORAGE_BASE_URL = "git.storage";
8
- var GitStorage = class {
243
+ var API_VERSION = 1;
244
+ var apiInstanceMap = /* @__PURE__ */ new Map();
245
+ function getApiInstance(baseUrl, version) {
246
+ if (!apiInstanceMap.has(`${baseUrl}--${version}`)) {
247
+ apiInstanceMap.set(`${baseUrl}--${version}`, new ApiFetcher(baseUrl, version));
248
+ }
249
+ return apiInstanceMap.get(`${baseUrl}--${version}`);
250
+ }
251
+ var RepoImpl = class {
252
+ constructor(id, options, generateJWT) {
253
+ this.id = id;
254
+ this.options = options;
255
+ this.generateJWT = generateJWT;
256
+ this.api = getApiInstance(
257
+ this.options.apiBaseUrl ?? API_BASE_URL,
258
+ this.options.apiVersion ?? API_VERSION
259
+ );
260
+ }
261
+ api;
262
+ async getRemoteURL(urlOptions) {
263
+ const storageBaseUrl = this.options.storageBaseUrl ?? STORAGE_BASE_URL;
264
+ const url = new URL(`https://${this.options.name}.${storageBaseUrl}/${this.id}.git`);
265
+ url.username = `t`;
266
+ url.password = await this.generateJWT(this.id, urlOptions);
267
+ return url.toString();
268
+ }
269
+ async getFile(options) {
270
+ const jwt = await this.generateJWT(this.id, {
271
+ permissions: ["git:read"],
272
+ ttl: options?.ttl ?? 1 * 60 * 60
273
+ // 1hr in seconds
274
+ });
275
+ const params = {
276
+ path: options.path
277
+ };
278
+ if (options.ref) {
279
+ params.ref = options.ref;
280
+ }
281
+ const response = await this.api.get({ path: "repos/file", params }, jwt);
282
+ return await response.json();
283
+ }
284
+ async listFiles(options) {
285
+ const jwt = await this.generateJWT(this.id, {
286
+ permissions: ["git:read"],
287
+ ttl: options?.ttl ?? 1 * 60 * 60
288
+ // 1hr in seconds
289
+ });
290
+ const params = options?.ref ? { ref: options.ref } : void 0;
291
+ const response = await this.api.get({ path: "repos/files", params }, jwt);
292
+ return await response.json();
293
+ }
294
+ async listBranches(options) {
295
+ const jwt = await this.generateJWT(this.id, {
296
+ permissions: ["git:read"],
297
+ ttl: options?.ttl ?? 1 * 60 * 60
298
+ // 1hr in seconds
299
+ });
300
+ let params;
301
+ if (options?.cursor || !options?.limit) {
302
+ params = {};
303
+ if (options?.cursor) {
304
+ params.cursor = options.cursor;
305
+ }
306
+ if (typeof options?.limit == "number") {
307
+ params.limit = options.limit.toString();
308
+ }
309
+ }
310
+ const response = await this.api.get({ path: "repos/branches", params }, jwt);
311
+ return await response.json();
312
+ }
313
+ async listCommits(options) {
314
+ const jwt = await this.generateJWT(this.id, {
315
+ permissions: ["git:read"],
316
+ ttl: options?.ttl ?? 1 * 60 * 60
317
+ // 1hr in seconds
318
+ });
319
+ let params;
320
+ if (options?.branch || options?.cursor || options?.limit) {
321
+ params = {};
322
+ if (options?.branch) {
323
+ params.branch = options.branch;
324
+ }
325
+ if (options?.cursor) {
326
+ params.cursor = options.cursor;
327
+ }
328
+ if (typeof options?.limit == "number") {
329
+ params.limit = options.limit.toString();
330
+ }
331
+ }
332
+ const response = await this.api.get({ path: "repos/commits", params }, jwt);
333
+ return await response.json();
334
+ }
335
+ async getBranchDiff(options) {
336
+ const jwt = await this.generateJWT(this.id, {
337
+ permissions: ["git:read"],
338
+ ttl: options?.ttl ?? 1 * 60 * 60
339
+ // 1hr in seconds
340
+ });
341
+ const params = {
342
+ branch: options.branch
343
+ };
344
+ if (options.base) {
345
+ params.base = options.base;
346
+ }
347
+ const response = await this.api.get({ path: "repos/branches/diff", params }, jwt);
348
+ return await response.json();
349
+ }
350
+ async getCommitDiff(options) {
351
+ const jwt = await this.generateJWT(this.id, {
352
+ permissions: ["git:read"],
353
+ ttl: options?.ttl ?? 1 * 60 * 60
354
+ // 1hr in seconds
355
+ });
356
+ const params = {
357
+ sha: options.sha
358
+ };
359
+ const response = await this.api.get({ path: "repos/diff", params }, jwt);
360
+ return await response.json();
361
+ }
362
+ async getCommit(options) {
363
+ const jwt = await this.generateJWT(this.id, {
364
+ permissions: ["git:read"],
365
+ ttl: options?.ttl ?? 1 * 60 * 60
366
+ // 1hr in seconds
367
+ });
368
+ const params = {
369
+ repo: this.id,
370
+ sha: options.sha
371
+ };
372
+ const response = await this.api.get({ path: "commit", params }, jwt);
373
+ return await response.json();
374
+ }
375
+ async repull(options) {
376
+ const jwt = await this.generateJWT(this.id, {
377
+ permissions: ["git:write"],
378
+ ttl: options?.ttl ?? 1 * 60 * 60
379
+ // 1hr in seconds
380
+ });
381
+ const body = {};
382
+ if (options.ref) {
383
+ body.ref = options.ref;
384
+ }
385
+ const response = await this.api.post({ path: "repos/repull", body }, jwt);
386
+ if (response.status !== 202) {
387
+ throw new Error(`Repull failed: ${response.status} ${await response.text()}`);
388
+ }
389
+ return;
390
+ }
391
+ };
392
+ var GitStorage = class _GitStorage {
393
+ static overrides = {};
9
394
  options;
395
+ api;
10
396
  constructor(options) {
11
397
  if (!options || options.name === void 0 || options.key === void 0 || options.name === null || options.key === null) {
12
398
  throw new Error(
@@ -19,11 +405,21 @@ var GitStorage = class {
19
405
  if (typeof options.key !== "string" || options.key.trim() === "") {
20
406
  throw new Error("GitStorage key must be a non-empty string.");
21
407
  }
408
+ const resolvedApiBaseUrl = options.apiBaseUrl ?? _GitStorage.overrides.apiBaseUrl ?? API_BASE_URL;
409
+ const resolvedApiVersion = options.apiVersion ?? _GitStorage.overrides.apiVersion ?? API_VERSION;
410
+ const resolvedStorageBaseUrl = options.storageBaseUrl ?? _GitStorage.overrides.storageBaseUrl ?? STORAGE_BASE_URL;
411
+ this.api = getApiInstance(resolvedApiBaseUrl, resolvedApiVersion);
22
412
  this.options = {
23
413
  key: options.key,
24
- name: options.name
414
+ name: options.name,
415
+ apiBaseUrl: resolvedApiBaseUrl,
416
+ apiVersion: resolvedApiVersion,
417
+ storageBaseUrl: resolvedStorageBaseUrl
25
418
  };
26
419
  }
420
+ static override(options) {
421
+ this.overrides = Object.assign({}, this.overrides, options);
422
+ }
27
423
  /**
28
424
  * Create a new repository
29
425
  * @returns The created repository
@@ -32,27 +428,24 @@ var GitStorage = class {
32
428
  const repoId = options?.id || crypto.randomUUID();
33
429
  const jwt = await this.generateJWT(repoId, {
34
430
  permissions: ["repo:write"],
35
- ttl: 1 * 60 * 60
431
+ ttl: options?.ttl ?? 1 * 60 * 60
36
432
  // 1hr in seconds
37
433
  });
38
- const response = await fetch(`${API_BASE_URL}/api/v1/repos`, {
39
- method: "POST",
40
- headers: {
41
- Authorization: `Bearer ${jwt}`
434
+ const baseRepoOptions = options?.baseRepo ? {
435
+ provider: "github",
436
+ ...snakecaseKeys__default.default(options.baseRepo)
437
+ } : null;
438
+ const createRepoPath = baseRepoOptions ? {
439
+ path: "repos",
440
+ body: {
441
+ base_repo: baseRepoOptions
42
442
  }
43
- });
44
- if (!response.ok) {
45
- throw new Error(`Failed to create repository: ${response.statusText}`);
443
+ } : "repos";
444
+ const resp = await this.api.post(createRepoPath, jwt, { allowedStatus: [409] });
445
+ if (resp.status === 409) {
446
+ throw new Error("Repository already exists");
46
447
  }
47
- return {
48
- id: repoId,
49
- getRemoteURL: async (urlOptions) => {
50
- const url = new URL(`https://${this.options.name}.${STORAGE_BASE_URL}/${repoId}.git`);
51
- url.username = `t`;
52
- url.password = await this.generateJWT(repoId, urlOptions);
53
- return url.toString();
54
- }
55
- };
448
+ return new RepoImpl(repoId, this.options, this.generateJWT.bind(this));
56
449
  }
57
450
  /**
58
451
  * Find a repository by ID
@@ -60,15 +453,15 @@ var GitStorage = class {
60
453
  * @returns The found repository
61
454
  */
62
455
  async findOne(options) {
63
- return {
64
- id: options.id,
65
- getRemoteURL: async (urlOptions) => {
66
- const url = new URL(`https://${this.options.name}.${STORAGE_BASE_URL}/${options.id}.git`);
67
- url.username = `t`;
68
- url.password = await this.generateJWT(options.id, urlOptions);
69
- return url.toString();
70
- }
71
- };
456
+ const jwt = await this.generateJWT(options.id, {
457
+ permissions: ["git:read"],
458
+ ttl: 1 * 60 * 60
459
+ });
460
+ const resp = await this.api.get("repo", jwt, { allowedStatus: [404] });
461
+ if (resp.status === 404) {
462
+ return null;
463
+ }
464
+ return new RepoImpl(options.id, this.options, this.generateJWT.bind(this));
72
465
  }
73
466
  /**
74
467
  * Get the current configuration
@@ -104,5 +497,8 @@ function createClient(options) {
104
497
 
105
498
  exports.GitStorage = GitStorage;
106
499
  exports.createClient = createClient;
500
+ exports.parseSignatureHeader = parseSignatureHeader;
501
+ exports.validateWebhook = validateWebhook;
502
+ exports.validateWebhookSignature = validateWebhookSignature;
107
503
  //# sourceMappingURL=index.cjs.map
108
504
  //# sourceMappingURL=index.cjs.map