@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 +170 -0
- package/dist/index.cjs +424 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +271 -8
- package/dist/index.d.ts +271 -8
- package/dist/index.js +418 -29
- package/dist/index.js.map +1 -1
- package/package.json +37 -37
- package/src/fetch.ts +71 -0
- package/src/index.ts +273 -53
- package/src/types.ts +237 -2
- package/src/util.ts +62 -0
- package/src/webhook.ts +251 -0
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
|
|
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
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|