@pierre/storage 0.0.10 → 0.1.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 +209 -46
- package/dist/index.cjs +1079 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +578 -62
- package/dist/index.d.ts +578 -62
- package/dist/index.js +1078 -51
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/commit.ts +649 -0
- package/src/errors.ts +50 -0
- package/src/fetch.ts +75 -5
- package/src/index.ts +408 -44
- package/src/schemas.ts +138 -0
- package/src/types.ts +211 -62
- package/src/util.ts +0 -18
- package/src/webhook.ts +75 -3
package/dist/index.js
CHANGED
|
@@ -1,14 +1,658 @@
|
|
|
1
1
|
import { importPKCS8, SignJWT } from 'jose';
|
|
2
2
|
import snakecaseKeys from 'snakecase-keys';
|
|
3
|
+
import { z } from 'zod';
|
|
3
4
|
|
|
4
5
|
// src/index.ts
|
|
5
6
|
|
|
7
|
+
// src/errors.ts
|
|
8
|
+
var RefUpdateError = class extends Error {
|
|
9
|
+
status;
|
|
10
|
+
reason;
|
|
11
|
+
refUpdate;
|
|
12
|
+
constructor(message, options) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "RefUpdateError";
|
|
15
|
+
this.status = options.status;
|
|
16
|
+
this.reason = options.reason ?? inferRefUpdateReason(options.status);
|
|
17
|
+
this.refUpdate = options.refUpdate;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var REF_REASON_MAP = {
|
|
21
|
+
precondition_failed: "precondition_failed",
|
|
22
|
+
conflict: "conflict",
|
|
23
|
+
not_found: "not_found",
|
|
24
|
+
invalid: "invalid",
|
|
25
|
+
timeout: "timeout",
|
|
26
|
+
unauthorized: "unauthorized",
|
|
27
|
+
forbidden: "forbidden",
|
|
28
|
+
unavailable: "unavailable",
|
|
29
|
+
internal: "internal",
|
|
30
|
+
failed: "failed",
|
|
31
|
+
ok: "unknown"
|
|
32
|
+
};
|
|
33
|
+
function inferRefUpdateReason(status) {
|
|
34
|
+
if (!status) {
|
|
35
|
+
return "unknown";
|
|
36
|
+
}
|
|
37
|
+
const trimmed = status.trim();
|
|
38
|
+
if (trimmed === "") {
|
|
39
|
+
return "unknown";
|
|
40
|
+
}
|
|
41
|
+
const label = trimmed.toLowerCase();
|
|
42
|
+
return REF_REASON_MAP[label] ?? "unknown";
|
|
43
|
+
}
|
|
44
|
+
var listFilesResponseSchema = z.object({
|
|
45
|
+
paths: z.array(z.string()),
|
|
46
|
+
ref: z.string()
|
|
47
|
+
});
|
|
48
|
+
var branchInfoSchema = z.object({
|
|
49
|
+
cursor: z.string(),
|
|
50
|
+
name: z.string(),
|
|
51
|
+
head_sha: z.string(),
|
|
52
|
+
created_at: z.string()
|
|
53
|
+
});
|
|
54
|
+
var listBranchesResponseSchema = z.object({
|
|
55
|
+
branches: z.array(branchInfoSchema),
|
|
56
|
+
next_cursor: z.string().nullable().optional(),
|
|
57
|
+
has_more: z.boolean()
|
|
58
|
+
});
|
|
59
|
+
var commitInfoRawSchema = z.object({
|
|
60
|
+
sha: z.string(),
|
|
61
|
+
message: z.string(),
|
|
62
|
+
author_name: z.string(),
|
|
63
|
+
author_email: z.string(),
|
|
64
|
+
committer_name: z.string(),
|
|
65
|
+
committer_email: z.string(),
|
|
66
|
+
date: z.string()
|
|
67
|
+
});
|
|
68
|
+
var listCommitsResponseSchema = z.object({
|
|
69
|
+
commits: z.array(commitInfoRawSchema),
|
|
70
|
+
next_cursor: z.string().nullable().optional(),
|
|
71
|
+
has_more: z.boolean()
|
|
72
|
+
});
|
|
73
|
+
var diffStatsSchema = z.object({
|
|
74
|
+
files: z.number(),
|
|
75
|
+
additions: z.number(),
|
|
76
|
+
deletions: z.number(),
|
|
77
|
+
changes: z.number()
|
|
78
|
+
});
|
|
79
|
+
var diffFileRawSchema = z.object({
|
|
80
|
+
path: z.string(),
|
|
81
|
+
state: z.string(),
|
|
82
|
+
old_path: z.string().nullable().optional(),
|
|
83
|
+
raw: z.string(),
|
|
84
|
+
bytes: z.number(),
|
|
85
|
+
is_eof: z.boolean()
|
|
86
|
+
});
|
|
87
|
+
var filteredFileRawSchema = z.object({
|
|
88
|
+
path: z.string(),
|
|
89
|
+
state: z.string(),
|
|
90
|
+
old_path: z.string().nullable().optional(),
|
|
91
|
+
bytes: z.number(),
|
|
92
|
+
is_eof: z.boolean()
|
|
93
|
+
});
|
|
94
|
+
var branchDiffResponseSchema = z.object({
|
|
95
|
+
branch: z.string(),
|
|
96
|
+
base: z.string(),
|
|
97
|
+
stats: diffStatsSchema,
|
|
98
|
+
files: z.array(diffFileRawSchema),
|
|
99
|
+
filtered_files: z.array(filteredFileRawSchema)
|
|
100
|
+
});
|
|
101
|
+
var commitDiffResponseSchema = z.object({
|
|
102
|
+
sha: z.string(),
|
|
103
|
+
stats: diffStatsSchema,
|
|
104
|
+
files: z.array(diffFileRawSchema),
|
|
105
|
+
filtered_files: z.array(filteredFileRawSchema)
|
|
106
|
+
});
|
|
107
|
+
var refUpdateResultSchema = z.object({
|
|
108
|
+
branch: z.string(),
|
|
109
|
+
old_sha: z.string(),
|
|
110
|
+
new_sha: z.string(),
|
|
111
|
+
success: z.boolean(),
|
|
112
|
+
status: z.string(),
|
|
113
|
+
message: z.string().optional()
|
|
114
|
+
});
|
|
115
|
+
var commitPackCommitSchema = z.object({
|
|
116
|
+
commit_sha: z.string(),
|
|
117
|
+
tree_sha: z.string(),
|
|
118
|
+
target_branch: z.string(),
|
|
119
|
+
pack_bytes: z.number(),
|
|
120
|
+
blob_count: z.number()
|
|
121
|
+
});
|
|
122
|
+
var resetCommitCommitSchema = commitPackCommitSchema.omit({ blob_count: true });
|
|
123
|
+
var refUpdateResultWithOptionalsSchema = z.object({
|
|
124
|
+
branch: z.string().optional(),
|
|
125
|
+
old_sha: z.string().optional(),
|
|
126
|
+
new_sha: z.string().optional(),
|
|
127
|
+
success: z.boolean().optional(),
|
|
128
|
+
status: z.string(),
|
|
129
|
+
message: z.string().optional()
|
|
130
|
+
});
|
|
131
|
+
var commitPackAckSchema = z.object({
|
|
132
|
+
commit: commitPackCommitSchema,
|
|
133
|
+
result: refUpdateResultSchema
|
|
134
|
+
});
|
|
135
|
+
var resetCommitAckSchema = z.object({
|
|
136
|
+
commit: resetCommitCommitSchema,
|
|
137
|
+
result: refUpdateResultSchema.extend({ success: z.literal(true) })
|
|
138
|
+
});
|
|
139
|
+
var commitPackResponseSchema = z.object({
|
|
140
|
+
commit: commitPackCommitSchema.partial().optional().nullable(),
|
|
141
|
+
result: refUpdateResultWithOptionalsSchema
|
|
142
|
+
});
|
|
143
|
+
var resetCommitResponseSchema = z.object({
|
|
144
|
+
commit: resetCommitCommitSchema.partial().optional().nullable(),
|
|
145
|
+
result: refUpdateResultWithOptionalsSchema
|
|
146
|
+
});
|
|
147
|
+
var errorEnvelopeSchema = z.object({
|
|
148
|
+
error: z.string()
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// src/commit.ts
|
|
152
|
+
var MAX_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
153
|
+
var DEFAULT_TTL_SECONDS = 60 * 60;
|
|
154
|
+
var BufferCtor = globalThis.Buffer;
|
|
155
|
+
var CommitBuilderImpl = class {
|
|
156
|
+
options;
|
|
157
|
+
getAuthToken;
|
|
158
|
+
transport;
|
|
159
|
+
operations = [];
|
|
160
|
+
sent = false;
|
|
161
|
+
constructor(deps) {
|
|
162
|
+
this.options = { ...deps.options };
|
|
163
|
+
this.getAuthToken = deps.getAuthToken;
|
|
164
|
+
this.transport = deps.transport;
|
|
165
|
+
const trimmedTarget = this.options.targetBranch?.trim();
|
|
166
|
+
const trimmedMessage = this.options.commitMessage?.trim();
|
|
167
|
+
const trimmedAuthorName = this.options.author?.name?.trim();
|
|
168
|
+
const trimmedAuthorEmail = this.options.author?.email?.trim();
|
|
169
|
+
if (!trimmedTarget) {
|
|
170
|
+
throw new Error("createCommit targetBranch is required");
|
|
171
|
+
}
|
|
172
|
+
if (trimmedTarget.startsWith("refs/")) {
|
|
173
|
+
throw new Error("createCommit targetBranch must not include refs/ prefix");
|
|
174
|
+
}
|
|
175
|
+
if (!trimmedMessage) {
|
|
176
|
+
throw new Error("createCommit commitMessage is required");
|
|
177
|
+
}
|
|
178
|
+
if (!trimmedAuthorName || !trimmedAuthorEmail) {
|
|
179
|
+
throw new Error("createCommit author name and email are required");
|
|
180
|
+
}
|
|
181
|
+
this.options.targetBranch = trimmedTarget;
|
|
182
|
+
this.options.commitMessage = trimmedMessage;
|
|
183
|
+
this.options.author = {
|
|
184
|
+
name: trimmedAuthorName,
|
|
185
|
+
email: trimmedAuthorEmail
|
|
186
|
+
};
|
|
187
|
+
if (typeof this.options.expectedHeadSha === "string") {
|
|
188
|
+
this.options.expectedHeadSha = this.options.expectedHeadSha.trim();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
addFile(path, source, options) {
|
|
192
|
+
this.ensureNotSent();
|
|
193
|
+
const normalizedPath = this.normalizePath(path);
|
|
194
|
+
const contentId = randomContentId();
|
|
195
|
+
const mode = options?.mode ?? "100644";
|
|
196
|
+
this.operations.push({
|
|
197
|
+
path: normalizedPath,
|
|
198
|
+
contentId,
|
|
199
|
+
mode,
|
|
200
|
+
operation: "upsert",
|
|
201
|
+
streamFactory: () => toAsyncIterable(source)
|
|
202
|
+
});
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
addFileFromString(path, contents, options) {
|
|
206
|
+
const encoding = options?.encoding ?? "utf8";
|
|
207
|
+
const normalizedEncoding = encoding === "utf-8" ? "utf8" : encoding;
|
|
208
|
+
let data;
|
|
209
|
+
if (normalizedEncoding === "utf8") {
|
|
210
|
+
data = new TextEncoder().encode(contents);
|
|
211
|
+
} else if (BufferCtor) {
|
|
212
|
+
data = BufferCtor.from(
|
|
213
|
+
contents,
|
|
214
|
+
normalizedEncoding
|
|
215
|
+
);
|
|
216
|
+
} else {
|
|
217
|
+
throw new Error(
|
|
218
|
+
`Unsupported encoding "${encoding}" in this environment. Non-UTF encodings require Node.js Buffer support.`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return this.addFile(path, data, options);
|
|
222
|
+
}
|
|
223
|
+
deletePath(path) {
|
|
224
|
+
this.ensureNotSent();
|
|
225
|
+
const normalizedPath = this.normalizePath(path);
|
|
226
|
+
this.operations.push({
|
|
227
|
+
path: normalizedPath,
|
|
228
|
+
contentId: randomContentId(),
|
|
229
|
+
operation: "delete"
|
|
230
|
+
});
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
async send() {
|
|
234
|
+
this.ensureNotSent();
|
|
235
|
+
this.sent = true;
|
|
236
|
+
const metadata = this.buildMetadata();
|
|
237
|
+
const blobEntries = this.operations.filter((op) => op.operation === "upsert" && op.streamFactory).map((op) => ({
|
|
238
|
+
contentId: op.contentId,
|
|
239
|
+
chunks: chunkify(op.streamFactory())
|
|
240
|
+
}));
|
|
241
|
+
const authorization = await this.getAuthToken();
|
|
242
|
+
const ack = await this.transport.send({
|
|
243
|
+
authorization,
|
|
244
|
+
signal: this.options.signal,
|
|
245
|
+
metadata,
|
|
246
|
+
blobs: blobEntries
|
|
247
|
+
});
|
|
248
|
+
return buildCommitResult(ack);
|
|
249
|
+
}
|
|
250
|
+
buildMetadata() {
|
|
251
|
+
const files = this.operations.map((op) => {
|
|
252
|
+
const entry = {
|
|
253
|
+
path: op.path,
|
|
254
|
+
content_id: op.contentId,
|
|
255
|
+
operation: op.operation
|
|
256
|
+
};
|
|
257
|
+
if (op.mode) {
|
|
258
|
+
entry.mode = op.mode;
|
|
259
|
+
}
|
|
260
|
+
return entry;
|
|
261
|
+
});
|
|
262
|
+
const metadata = {
|
|
263
|
+
target_branch: this.options.targetBranch,
|
|
264
|
+
commit_message: this.options.commitMessage,
|
|
265
|
+
author: {
|
|
266
|
+
name: this.options.author.name,
|
|
267
|
+
email: this.options.author.email
|
|
268
|
+
},
|
|
269
|
+
files
|
|
270
|
+
};
|
|
271
|
+
if (this.options.expectedHeadSha) {
|
|
272
|
+
metadata.expected_head_sha = this.options.expectedHeadSha;
|
|
273
|
+
}
|
|
274
|
+
if (this.options.committer) {
|
|
275
|
+
metadata.committer = {
|
|
276
|
+
name: this.options.committer.name,
|
|
277
|
+
email: this.options.committer.email
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
return metadata;
|
|
281
|
+
}
|
|
282
|
+
ensureNotSent() {
|
|
283
|
+
if (this.sent) {
|
|
284
|
+
throw new Error("createCommit builder cannot be reused after send()");
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
normalizePath(path) {
|
|
288
|
+
if (!path || typeof path !== "string" || path.trim() === "") {
|
|
289
|
+
throw new Error("File path must be a non-empty string");
|
|
290
|
+
}
|
|
291
|
+
return path.replace(/^\//, "");
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
var FetchCommitTransport = class {
|
|
295
|
+
url;
|
|
296
|
+
constructor(config) {
|
|
297
|
+
const trimmedBase = config.baseUrl.replace(/\/+$/, "");
|
|
298
|
+
this.url = `${trimmedBase}/api/v${config.version}/repos/commit-pack`;
|
|
299
|
+
}
|
|
300
|
+
async send(request) {
|
|
301
|
+
const bodyIterable = buildMessageIterable(request.metadata, request.blobs);
|
|
302
|
+
const body = toRequestBody(bodyIterable);
|
|
303
|
+
const response = await fetch(this.url, {
|
|
304
|
+
method: "POST",
|
|
305
|
+
headers: {
|
|
306
|
+
Authorization: `Bearer ${request.authorization}`,
|
|
307
|
+
"Content-Type": "application/x-ndjson",
|
|
308
|
+
Accept: "application/json"
|
|
309
|
+
},
|
|
310
|
+
body,
|
|
311
|
+
signal: request.signal
|
|
312
|
+
});
|
|
313
|
+
if (!response.ok) {
|
|
314
|
+
const { statusMessage, statusLabel, refUpdate } = await parseCommitPackError(response);
|
|
315
|
+
throw new RefUpdateError(statusMessage, {
|
|
316
|
+
status: statusLabel,
|
|
317
|
+
message: statusMessage,
|
|
318
|
+
refUpdate
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
const ack = commitPackAckSchema.parse(await response.json());
|
|
322
|
+
return ack;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
function toRequestBody(iterable) {
|
|
326
|
+
const readableStreamCtor = globalThis.ReadableStream;
|
|
327
|
+
if (typeof readableStreamCtor === "function") {
|
|
328
|
+
const iterator = iterable[Symbol.asyncIterator]();
|
|
329
|
+
return new readableStreamCtor({
|
|
330
|
+
async pull(controller) {
|
|
331
|
+
const { value, done } = await iterator.next();
|
|
332
|
+
if (done) {
|
|
333
|
+
controller.close();
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
controller.enqueue(value);
|
|
337
|
+
},
|
|
338
|
+
async cancel(reason) {
|
|
339
|
+
if (typeof iterator.return === "function") {
|
|
340
|
+
await iterator.return(reason);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
return iterable;
|
|
346
|
+
}
|
|
347
|
+
function buildMessageIterable(metadata, blobs) {
|
|
348
|
+
const encoder = new TextEncoder();
|
|
349
|
+
return {
|
|
350
|
+
async *[Symbol.asyncIterator]() {
|
|
351
|
+
yield encoder.encode(`${JSON.stringify({ metadata })}
|
|
352
|
+
`);
|
|
353
|
+
for (const blob of blobs) {
|
|
354
|
+
for await (const segment of blob.chunks) {
|
|
355
|
+
const payload = {
|
|
356
|
+
blob_chunk: {
|
|
357
|
+
content_id: blob.contentId,
|
|
358
|
+
data: base64Encode(segment.chunk),
|
|
359
|
+
eof: segment.eof
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
yield encoder.encode(`${JSON.stringify(payload)}
|
|
363
|
+
`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function buildCommitResult(ack) {
|
|
370
|
+
const refUpdate = toRefUpdate(ack.result);
|
|
371
|
+
if (!ack.result.success) {
|
|
372
|
+
throw new RefUpdateError(
|
|
373
|
+
ack.result.message ?? `Commit failed with status ${ack.result.status}`,
|
|
374
|
+
{
|
|
375
|
+
status: ack.result.status,
|
|
376
|
+
message: ack.result.message,
|
|
377
|
+
refUpdate
|
|
378
|
+
}
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
commitSha: ack.commit.commit_sha,
|
|
383
|
+
treeSha: ack.commit.tree_sha,
|
|
384
|
+
targetBranch: ack.commit.target_branch,
|
|
385
|
+
packBytes: ack.commit.pack_bytes,
|
|
386
|
+
blobCount: ack.commit.blob_count,
|
|
387
|
+
refUpdate
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function toRefUpdate(result) {
|
|
391
|
+
return {
|
|
392
|
+
branch: result.branch,
|
|
393
|
+
oldSha: result.old_sha,
|
|
394
|
+
newSha: result.new_sha
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
async function* chunkify(source) {
|
|
398
|
+
let pending = null;
|
|
399
|
+
let produced = false;
|
|
400
|
+
for await (const value of source) {
|
|
401
|
+
const bytes = value;
|
|
402
|
+
if (pending && pending.byteLength === MAX_CHUNK_BYTES) {
|
|
403
|
+
yield { chunk: pending, eof: false };
|
|
404
|
+
produced = true;
|
|
405
|
+
pending = null;
|
|
406
|
+
}
|
|
407
|
+
const merged = pending ? concatChunks(pending, bytes) : bytes;
|
|
408
|
+
pending = null;
|
|
409
|
+
let cursor = merged;
|
|
410
|
+
while (cursor.byteLength > MAX_CHUNK_BYTES) {
|
|
411
|
+
const chunk = cursor.slice(0, MAX_CHUNK_BYTES);
|
|
412
|
+
cursor = cursor.slice(MAX_CHUNK_BYTES);
|
|
413
|
+
yield { chunk, eof: false };
|
|
414
|
+
produced = true;
|
|
415
|
+
}
|
|
416
|
+
pending = cursor;
|
|
417
|
+
}
|
|
418
|
+
if (pending) {
|
|
419
|
+
yield { chunk: pending, eof: true };
|
|
420
|
+
produced = true;
|
|
421
|
+
}
|
|
422
|
+
if (!produced) {
|
|
423
|
+
yield { chunk: new Uint8Array(0), eof: true };
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
async function* toAsyncIterable(source) {
|
|
427
|
+
if (typeof source === "string") {
|
|
428
|
+
yield new TextEncoder().encode(source);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (source instanceof Uint8Array) {
|
|
432
|
+
yield source;
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (source instanceof ArrayBuffer) {
|
|
436
|
+
yield new Uint8Array(source);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (ArrayBuffer.isView(source)) {
|
|
440
|
+
yield new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (isBlobLike(source)) {
|
|
444
|
+
const stream = source.stream();
|
|
445
|
+
if (isAsyncIterable(stream)) {
|
|
446
|
+
for await (const chunk of stream) {
|
|
447
|
+
yield ensureUint8Array(chunk);
|
|
448
|
+
}
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (isReadableStreamLike(stream)) {
|
|
452
|
+
yield* readReadableStream(stream);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (isReadableStreamLike(source)) {
|
|
457
|
+
yield* readReadableStream(source);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (isAsyncIterable(source)) {
|
|
461
|
+
for await (const chunk of source) {
|
|
462
|
+
yield ensureUint8Array(chunk);
|
|
463
|
+
}
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (isIterable(source)) {
|
|
467
|
+
for (const chunk of source) {
|
|
468
|
+
yield ensureUint8Array(chunk);
|
|
469
|
+
}
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
throw new Error("Unsupported file source for createCommit");
|
|
473
|
+
}
|
|
474
|
+
async function* readReadableStream(stream) {
|
|
475
|
+
const reader = stream.getReader();
|
|
476
|
+
try {
|
|
477
|
+
while (true) {
|
|
478
|
+
const { value, done } = await reader.read();
|
|
479
|
+
if (done) {
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
if (value !== void 0) {
|
|
483
|
+
yield ensureUint8Array(value);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
} finally {
|
|
487
|
+
reader.releaseLock?.();
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function ensureUint8Array(value) {
|
|
491
|
+
if (value instanceof Uint8Array) {
|
|
492
|
+
return value;
|
|
493
|
+
}
|
|
494
|
+
if (value instanceof ArrayBuffer) {
|
|
495
|
+
return new Uint8Array(value);
|
|
496
|
+
}
|
|
497
|
+
if (ArrayBuffer.isView(value)) {
|
|
498
|
+
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
499
|
+
}
|
|
500
|
+
if (typeof value === "string") {
|
|
501
|
+
return new TextEncoder().encode(value);
|
|
502
|
+
}
|
|
503
|
+
if (BufferCtor && BufferCtor.isBuffer(value)) {
|
|
504
|
+
return value;
|
|
505
|
+
}
|
|
506
|
+
throw new Error("Unsupported chunk type; expected binary data");
|
|
507
|
+
}
|
|
508
|
+
function isBlobLike(value) {
|
|
509
|
+
return typeof value === "object" && value !== null && typeof value.stream === "function";
|
|
510
|
+
}
|
|
511
|
+
function isReadableStreamLike(value) {
|
|
512
|
+
return typeof value === "object" && value !== null && typeof value.getReader === "function";
|
|
513
|
+
}
|
|
514
|
+
function isAsyncIterable(value) {
|
|
515
|
+
return typeof value === "object" && value !== null && Symbol.asyncIterator in value;
|
|
516
|
+
}
|
|
517
|
+
function isIterable(value) {
|
|
518
|
+
return typeof value === "object" && value !== null && Symbol.iterator in value;
|
|
519
|
+
}
|
|
520
|
+
function concatChunks(a, b) {
|
|
521
|
+
if (a.byteLength === 0) {
|
|
522
|
+
return b;
|
|
523
|
+
}
|
|
524
|
+
if (b.byteLength === 0) {
|
|
525
|
+
return a;
|
|
526
|
+
}
|
|
527
|
+
const merged = new Uint8Array(a.byteLength + b.byteLength);
|
|
528
|
+
merged.set(a, 0);
|
|
529
|
+
merged.set(b, a.byteLength);
|
|
530
|
+
return merged;
|
|
531
|
+
}
|
|
532
|
+
function base64Encode(bytes) {
|
|
533
|
+
if (BufferCtor) {
|
|
534
|
+
return BufferCtor.from(bytes).toString("base64");
|
|
535
|
+
}
|
|
536
|
+
let binary = "";
|
|
537
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
538
|
+
binary += String.fromCharCode(bytes[i]);
|
|
539
|
+
}
|
|
540
|
+
const btoaFn = globalThis.btoa;
|
|
541
|
+
if (typeof btoaFn === "function") {
|
|
542
|
+
return btoaFn(binary);
|
|
543
|
+
}
|
|
544
|
+
throw new Error("Base64 encoding is not supported in this environment");
|
|
545
|
+
}
|
|
546
|
+
function randomContentId() {
|
|
547
|
+
const cryptoObj = globalThis.crypto;
|
|
548
|
+
if (cryptoObj && typeof cryptoObj.randomUUID === "function") {
|
|
549
|
+
return cryptoObj.randomUUID();
|
|
550
|
+
}
|
|
551
|
+
const random = Math.random().toString(36).slice(2);
|
|
552
|
+
return `cid-${Date.now().toString(36)}-${random}`;
|
|
553
|
+
}
|
|
554
|
+
function createCommitBuilder(deps) {
|
|
555
|
+
return new CommitBuilderImpl(deps);
|
|
556
|
+
}
|
|
557
|
+
function resolveCommitTtlSeconds(options) {
|
|
558
|
+
if (typeof options?.ttl === "number" && options.ttl > 0) {
|
|
559
|
+
return options.ttl;
|
|
560
|
+
}
|
|
561
|
+
return DEFAULT_TTL_SECONDS;
|
|
562
|
+
}
|
|
563
|
+
async function parseCommitPackError(response) {
|
|
564
|
+
const fallbackMessage = `createCommit request failed (${response.status} ${response.statusText})`;
|
|
565
|
+
const cloned = response.clone();
|
|
566
|
+
let jsonBody;
|
|
567
|
+
try {
|
|
568
|
+
jsonBody = await cloned.json();
|
|
569
|
+
} catch {
|
|
570
|
+
jsonBody = void 0;
|
|
571
|
+
}
|
|
572
|
+
let textBody;
|
|
573
|
+
if (jsonBody === void 0) {
|
|
574
|
+
try {
|
|
575
|
+
textBody = await response.text();
|
|
576
|
+
} catch {
|
|
577
|
+
textBody = void 0;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
const defaultStatus = (() => {
|
|
581
|
+
const inferred = inferRefUpdateReason(String(response.status));
|
|
582
|
+
return inferred === "unknown" ? "failed" : inferred;
|
|
583
|
+
})();
|
|
584
|
+
let statusLabel = defaultStatus;
|
|
585
|
+
let refUpdate;
|
|
586
|
+
let message;
|
|
587
|
+
if (jsonBody !== void 0) {
|
|
588
|
+
const parsedResponse = commitPackResponseSchema.safeParse(jsonBody);
|
|
589
|
+
if (parsedResponse.success) {
|
|
590
|
+
const result = parsedResponse.data.result;
|
|
591
|
+
if (typeof result.status === "string" && result.status.trim() !== "") {
|
|
592
|
+
statusLabel = result.status.trim();
|
|
593
|
+
}
|
|
594
|
+
refUpdate = toPartialRefUpdateFields(result.branch, result.old_sha, result.new_sha);
|
|
595
|
+
if (typeof result.message === "string" && result.message.trim() !== "") {
|
|
596
|
+
message = result.message.trim();
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (!message) {
|
|
600
|
+
const parsedError = errorEnvelopeSchema.safeParse(jsonBody);
|
|
601
|
+
if (parsedError.success) {
|
|
602
|
+
const trimmed = parsedError.data.error.trim();
|
|
603
|
+
if (trimmed) {
|
|
604
|
+
message = trimmed;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (!message && typeof jsonBody === "string" && jsonBody.trim() !== "") {
|
|
610
|
+
message = jsonBody.trim();
|
|
611
|
+
}
|
|
612
|
+
if (!message && textBody && textBody.trim() !== "") {
|
|
613
|
+
message = textBody.trim();
|
|
614
|
+
}
|
|
615
|
+
return {
|
|
616
|
+
statusMessage: message ?? fallbackMessage,
|
|
617
|
+
statusLabel,
|
|
618
|
+
refUpdate
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
function toPartialRefUpdateFields(branch, oldSha, newSha) {
|
|
622
|
+
const refUpdate = {};
|
|
623
|
+
if (typeof branch === "string" && branch.trim() !== "") {
|
|
624
|
+
refUpdate.branch = branch.trim();
|
|
625
|
+
}
|
|
626
|
+
if (typeof oldSha === "string" && oldSha.trim() !== "") {
|
|
627
|
+
refUpdate.oldSha = oldSha.trim();
|
|
628
|
+
}
|
|
629
|
+
if (typeof newSha === "string" && newSha.trim() !== "") {
|
|
630
|
+
refUpdate.newSha = newSha.trim();
|
|
631
|
+
}
|
|
632
|
+
return Object.keys(refUpdate).length > 0 ? refUpdate : void 0;
|
|
633
|
+
}
|
|
634
|
+
|
|
6
635
|
// src/fetch.ts
|
|
636
|
+
var ApiError = class extends Error {
|
|
637
|
+
status;
|
|
638
|
+
statusText;
|
|
639
|
+
method;
|
|
640
|
+
url;
|
|
641
|
+
body;
|
|
642
|
+
constructor(params) {
|
|
643
|
+
super(params.message);
|
|
644
|
+
this.name = "ApiError";
|
|
645
|
+
this.status = params.status;
|
|
646
|
+
this.statusText = params.statusText;
|
|
647
|
+
this.method = params.method;
|
|
648
|
+
this.url = params.url;
|
|
649
|
+
this.body = params.body;
|
|
650
|
+
}
|
|
651
|
+
};
|
|
7
652
|
var ApiFetcher = class {
|
|
8
653
|
constructor(API_BASE_URL2, version) {
|
|
9
654
|
this.API_BASE_URL = API_BASE_URL2;
|
|
10
655
|
this.version = version;
|
|
11
|
-
console.log("api fetcher created", API_BASE_URL2, version);
|
|
12
656
|
}
|
|
13
657
|
getBaseUrl() {
|
|
14
658
|
return `${this.API_BASE_URL}/api/v${this.version}`;
|
|
@@ -38,9 +682,48 @@ var ApiFetcher = class {
|
|
|
38
682
|
const response = await fetch(requestUrl, requestOptions);
|
|
39
683
|
if (!response.ok) {
|
|
40
684
|
const allowed = options?.allowedStatus ?? [];
|
|
41
|
-
if (
|
|
42
|
-
|
|
685
|
+
if (allowed.includes(response.status)) {
|
|
686
|
+
return response;
|
|
687
|
+
}
|
|
688
|
+
let errorBody;
|
|
689
|
+
let message;
|
|
690
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
691
|
+
try {
|
|
692
|
+
if (contentType.includes("application/json")) {
|
|
693
|
+
errorBody = await response.json();
|
|
694
|
+
} else {
|
|
695
|
+
const text = await response.text();
|
|
696
|
+
errorBody = text;
|
|
697
|
+
}
|
|
698
|
+
} catch {
|
|
699
|
+
try {
|
|
700
|
+
errorBody = await response.text();
|
|
701
|
+
} catch {
|
|
702
|
+
errorBody = void 0;
|
|
703
|
+
}
|
|
43
704
|
}
|
|
705
|
+
if (typeof errorBody === "string") {
|
|
706
|
+
const trimmed = errorBody.trim();
|
|
707
|
+
if (trimmed) {
|
|
708
|
+
message = trimmed;
|
|
709
|
+
}
|
|
710
|
+
} else if (errorBody && typeof errorBody === "object") {
|
|
711
|
+
const parsedError = errorEnvelopeSchema.safeParse(errorBody);
|
|
712
|
+
if (parsedError.success) {
|
|
713
|
+
const trimmed = parsedError.data.error.trim();
|
|
714
|
+
if (trimmed) {
|
|
715
|
+
message = trimmed;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
throw new ApiError({
|
|
720
|
+
message: message ?? `Request ${method} ${requestUrl} failed with status ${response.status} ${response.statusText}`,
|
|
721
|
+
status: response.status,
|
|
722
|
+
statusText: response.statusText,
|
|
723
|
+
method,
|
|
724
|
+
url: requestUrl,
|
|
725
|
+
body: errorBody
|
|
726
|
+
});
|
|
44
727
|
}
|
|
45
728
|
return response;
|
|
46
729
|
}
|
|
@@ -213,9 +896,9 @@ async function validateWebhook(payload, headers, secret, options = {}) {
|
|
|
213
896
|
return validationResult;
|
|
214
897
|
}
|
|
215
898
|
const payloadStr = typeof payload === "string" ? payload : payload.toString("utf8");
|
|
216
|
-
let
|
|
899
|
+
let parsedJson;
|
|
217
900
|
try {
|
|
218
|
-
|
|
901
|
+
parsedJson = JSON.parse(payloadStr);
|
|
219
902
|
} catch {
|
|
220
903
|
return {
|
|
221
904
|
valid: false,
|
|
@@ -223,25 +906,283 @@ async function validateWebhook(payload, headers, secret, options = {}) {
|
|
|
223
906
|
timestamp: validationResult.timestamp
|
|
224
907
|
};
|
|
225
908
|
}
|
|
909
|
+
const conversion = convertWebhookPayload(String(eventType), parsedJson);
|
|
910
|
+
if (!conversion.valid) {
|
|
911
|
+
return {
|
|
912
|
+
valid: false,
|
|
913
|
+
error: conversion.error,
|
|
914
|
+
timestamp: validationResult.timestamp
|
|
915
|
+
};
|
|
916
|
+
}
|
|
226
917
|
return {
|
|
227
918
|
valid: true,
|
|
228
919
|
eventType,
|
|
229
920
|
timestamp: validationResult.timestamp,
|
|
230
|
-
payload:
|
|
921
|
+
payload: conversion.payload
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
function convertWebhookPayload(eventType, raw) {
|
|
925
|
+
if (eventType === "push") {
|
|
926
|
+
if (!isRawWebhookPushEvent(raw)) {
|
|
927
|
+
return {
|
|
928
|
+
valid: false,
|
|
929
|
+
error: "Invalid push payload"
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
return {
|
|
933
|
+
valid: true,
|
|
934
|
+
payload: transformPushEvent(raw)
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
const fallbackPayload = { type: eventType, raw };
|
|
938
|
+
return {
|
|
939
|
+
valid: true,
|
|
940
|
+
payload: fallbackPayload
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
function transformPushEvent(raw) {
|
|
944
|
+
return {
|
|
945
|
+
type: "push",
|
|
946
|
+
repository: {
|
|
947
|
+
id: raw.repository.id,
|
|
948
|
+
url: raw.repository.url
|
|
949
|
+
},
|
|
950
|
+
ref: raw.ref,
|
|
951
|
+
before: raw.before,
|
|
952
|
+
after: raw.after,
|
|
953
|
+
customerId: raw.customer_id,
|
|
954
|
+
pushedAt: new Date(raw.pushed_at),
|
|
955
|
+
rawPushedAt: raw.pushed_at
|
|
231
956
|
};
|
|
232
957
|
}
|
|
958
|
+
function isRawWebhookPushEvent(value) {
|
|
959
|
+
if (!isRecord(value)) {
|
|
960
|
+
return false;
|
|
961
|
+
}
|
|
962
|
+
if (!isRecord(value.repository)) {
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
return typeof value.repository.id === "string" && typeof value.repository.url === "string" && typeof value.ref === "string" && typeof value.before === "string" && typeof value.after === "string" && typeof value.customer_id === "string" && typeof value.pushed_at === "string";
|
|
966
|
+
}
|
|
967
|
+
function isRecord(value) {
|
|
968
|
+
return typeof value === "object" && value !== null;
|
|
969
|
+
}
|
|
233
970
|
|
|
234
971
|
// src/index.ts
|
|
235
|
-
var API_BASE_URL = "https://api.
|
|
236
|
-
var STORAGE_BASE_URL = "
|
|
972
|
+
var API_BASE_URL = "https://api.code.storage";
|
|
973
|
+
var STORAGE_BASE_URL = "code.storage";
|
|
237
974
|
var API_VERSION = 1;
|
|
238
975
|
var apiInstanceMap = /* @__PURE__ */ new Map();
|
|
976
|
+
var DEFAULT_TOKEN_TTL_SECONDS = 60 * 60;
|
|
977
|
+
var RESET_COMMIT_ALLOWED_STATUS = [
|
|
978
|
+
400,
|
|
979
|
+
// Bad Request - validation errors
|
|
980
|
+
401,
|
|
981
|
+
// Unauthorized - missing/invalid auth header
|
|
982
|
+
403,
|
|
983
|
+
// Forbidden - missing git:write scope
|
|
984
|
+
404,
|
|
985
|
+
// Not Found - repo lookup failures
|
|
986
|
+
408,
|
|
987
|
+
// Request Timeout - client cancelled
|
|
988
|
+
409,
|
|
989
|
+
// Conflict - concurrent ref updates
|
|
990
|
+
412,
|
|
991
|
+
// Precondition Failed - optimistic concurrency
|
|
992
|
+
422,
|
|
993
|
+
// Unprocessable Entity - metadata issues
|
|
994
|
+
429,
|
|
995
|
+
// Too Many Requests - upstream throttling
|
|
996
|
+
499,
|
|
997
|
+
// Client Closed Request - storage cancellation
|
|
998
|
+
500,
|
|
999
|
+
// Internal Server Error - generic failure
|
|
1000
|
+
502,
|
|
1001
|
+
// Bad Gateway - storage/gateway bridge issues
|
|
1002
|
+
503,
|
|
1003
|
+
// Service Unavailable - storage selection failures
|
|
1004
|
+
504
|
|
1005
|
+
// Gateway Timeout - long-running storage operations
|
|
1006
|
+
];
|
|
1007
|
+
function resolveInvocationTtlSeconds(options, defaultValue = DEFAULT_TOKEN_TTL_SECONDS) {
|
|
1008
|
+
if (typeof options?.ttl === "number" && options.ttl > 0) {
|
|
1009
|
+
return options.ttl;
|
|
1010
|
+
}
|
|
1011
|
+
return defaultValue;
|
|
1012
|
+
}
|
|
1013
|
+
function toRefUpdate2(result) {
|
|
1014
|
+
return {
|
|
1015
|
+
branch: result.branch,
|
|
1016
|
+
oldSha: result.old_sha,
|
|
1017
|
+
newSha: result.new_sha
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
function buildResetCommitResult(ack) {
|
|
1021
|
+
const refUpdate = toRefUpdate2(ack.result);
|
|
1022
|
+
if (!ack.result.success) {
|
|
1023
|
+
throw new RefUpdateError(
|
|
1024
|
+
ack.result.message ?? `Reset commit failed with status ${ack.result.status}`,
|
|
1025
|
+
{
|
|
1026
|
+
status: ack.result.status,
|
|
1027
|
+
message: ack.result.message,
|
|
1028
|
+
refUpdate
|
|
1029
|
+
}
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
return {
|
|
1033
|
+
commitSha: ack.commit.commit_sha,
|
|
1034
|
+
treeSha: ack.commit.tree_sha,
|
|
1035
|
+
targetBranch: ack.commit.target_branch,
|
|
1036
|
+
packBytes: ack.commit.pack_bytes,
|
|
1037
|
+
refUpdate
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function toPartialRefUpdate(branch, oldSha, newSha) {
|
|
1041
|
+
const refUpdate = {};
|
|
1042
|
+
if (typeof branch === "string" && branch.trim() !== "") {
|
|
1043
|
+
refUpdate.branch = branch;
|
|
1044
|
+
}
|
|
1045
|
+
if (typeof oldSha === "string" && oldSha.trim() !== "") {
|
|
1046
|
+
refUpdate.oldSha = oldSha;
|
|
1047
|
+
}
|
|
1048
|
+
if (typeof newSha === "string" && newSha.trim() !== "") {
|
|
1049
|
+
refUpdate.newSha = newSha;
|
|
1050
|
+
}
|
|
1051
|
+
return Object.keys(refUpdate).length > 0 ? refUpdate : void 0;
|
|
1052
|
+
}
|
|
1053
|
+
function parseResetCommitPayload(payload) {
|
|
1054
|
+
const ack = resetCommitAckSchema.safeParse(payload);
|
|
1055
|
+
if (ack.success) {
|
|
1056
|
+
return { ack: ack.data };
|
|
1057
|
+
}
|
|
1058
|
+
const failure = resetCommitResponseSchema.safeParse(payload);
|
|
1059
|
+
if (failure.success) {
|
|
1060
|
+
const result = failure.data.result;
|
|
1061
|
+
return {
|
|
1062
|
+
failure: {
|
|
1063
|
+
status: result.status,
|
|
1064
|
+
message: result.message,
|
|
1065
|
+
refUpdate: toPartialRefUpdate(result.branch, result.old_sha, result.new_sha)
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
return null;
|
|
1070
|
+
}
|
|
1071
|
+
function httpStatusToResetStatus(status) {
|
|
1072
|
+
switch (status) {
|
|
1073
|
+
case 409:
|
|
1074
|
+
return "conflict";
|
|
1075
|
+
case 412:
|
|
1076
|
+
return "precondition_failed";
|
|
1077
|
+
default:
|
|
1078
|
+
return `${status}`;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
239
1081
|
function getApiInstance(baseUrl, version) {
|
|
240
1082
|
if (!apiInstanceMap.has(`${baseUrl}--${version}`)) {
|
|
241
1083
|
apiInstanceMap.set(`${baseUrl}--${version}`, new ApiFetcher(baseUrl, version));
|
|
242
1084
|
}
|
|
243
1085
|
return apiInstanceMap.get(`${baseUrl}--${version}`);
|
|
244
1086
|
}
|
|
1087
|
+
function transformBranchInfo(raw) {
|
|
1088
|
+
return {
|
|
1089
|
+
cursor: raw.cursor,
|
|
1090
|
+
name: raw.name,
|
|
1091
|
+
headSha: raw.head_sha,
|
|
1092
|
+
createdAt: raw.created_at
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
function transformListBranchesResult(raw) {
|
|
1096
|
+
return {
|
|
1097
|
+
branches: raw.branches.map(transformBranchInfo),
|
|
1098
|
+
nextCursor: raw.next_cursor ?? void 0,
|
|
1099
|
+
hasMore: raw.has_more
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
function transformCommitInfo(raw) {
|
|
1103
|
+
const parsedDate = new Date(raw.date);
|
|
1104
|
+
return {
|
|
1105
|
+
sha: raw.sha,
|
|
1106
|
+
message: raw.message,
|
|
1107
|
+
authorName: raw.author_name,
|
|
1108
|
+
authorEmail: raw.author_email,
|
|
1109
|
+
committerName: raw.committer_name,
|
|
1110
|
+
committerEmail: raw.committer_email,
|
|
1111
|
+
date: parsedDate,
|
|
1112
|
+
rawDate: raw.date
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
function transformListCommitsResult(raw) {
|
|
1116
|
+
return {
|
|
1117
|
+
commits: raw.commits.map(transformCommitInfo),
|
|
1118
|
+
nextCursor: raw.next_cursor ?? void 0,
|
|
1119
|
+
hasMore: raw.has_more
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
function normalizeDiffState(rawState) {
|
|
1123
|
+
if (!rawState) {
|
|
1124
|
+
return "unknown";
|
|
1125
|
+
}
|
|
1126
|
+
const leading = rawState.trim()[0]?.toUpperCase();
|
|
1127
|
+
switch (leading) {
|
|
1128
|
+
case "A":
|
|
1129
|
+
return "added";
|
|
1130
|
+
case "M":
|
|
1131
|
+
return "modified";
|
|
1132
|
+
case "D":
|
|
1133
|
+
return "deleted";
|
|
1134
|
+
case "R":
|
|
1135
|
+
return "renamed";
|
|
1136
|
+
case "C":
|
|
1137
|
+
return "copied";
|
|
1138
|
+
case "T":
|
|
1139
|
+
return "type_changed";
|
|
1140
|
+
case "U":
|
|
1141
|
+
return "unmerged";
|
|
1142
|
+
default:
|
|
1143
|
+
return "unknown";
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
function transformFileDiff(raw) {
|
|
1147
|
+
const normalizedState = normalizeDiffState(raw.state);
|
|
1148
|
+
return {
|
|
1149
|
+
path: raw.path,
|
|
1150
|
+
state: normalizedState,
|
|
1151
|
+
rawState: raw.state,
|
|
1152
|
+
oldPath: raw.old_path ?? void 0,
|
|
1153
|
+
raw: raw.raw,
|
|
1154
|
+
bytes: raw.bytes,
|
|
1155
|
+
isEof: raw.is_eof
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
function transformFilteredFile(raw) {
|
|
1159
|
+
const normalizedState = normalizeDiffState(raw.state);
|
|
1160
|
+
return {
|
|
1161
|
+
path: raw.path,
|
|
1162
|
+
state: normalizedState,
|
|
1163
|
+
rawState: raw.state,
|
|
1164
|
+
oldPath: raw.old_path ?? void 0,
|
|
1165
|
+
bytes: raw.bytes,
|
|
1166
|
+
isEof: raw.is_eof
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
function transformBranchDiffResult(raw) {
|
|
1170
|
+
return {
|
|
1171
|
+
branch: raw.branch,
|
|
1172
|
+
base: raw.base,
|
|
1173
|
+
stats: raw.stats,
|
|
1174
|
+
files: raw.files.map(transformFileDiff),
|
|
1175
|
+
filteredFiles: raw.filtered_files.map(transformFilteredFile)
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
function transformCommitDiffResult(raw) {
|
|
1179
|
+
return {
|
|
1180
|
+
sha: raw.sha,
|
|
1181
|
+
stats: raw.stats,
|
|
1182
|
+
files: raw.files.map(transformFileDiff),
|
|
1183
|
+
filteredFiles: raw.filtered_files.map(transformFilteredFile)
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
245
1186
|
var RepoImpl = class {
|
|
246
1187
|
constructor(id, options, generateJWT) {
|
|
247
1188
|
this.id = id;
|
|
@@ -261,10 +1202,10 @@ var RepoImpl = class {
|
|
|
261
1202
|
return url.toString();
|
|
262
1203
|
}
|
|
263
1204
|
async getFileStream(options) {
|
|
1205
|
+
const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
|
|
264
1206
|
const jwt = await this.generateJWT(this.id, {
|
|
265
1207
|
permissions: ["git:read"],
|
|
266
|
-
ttl
|
|
267
|
-
// 1hr in seconds
|
|
1208
|
+
ttl
|
|
268
1209
|
});
|
|
269
1210
|
const params = {
|
|
270
1211
|
path: options.path
|
|
@@ -275,39 +1216,46 @@ var RepoImpl = class {
|
|
|
275
1216
|
return this.api.get({ path: "repos/file", params }, jwt);
|
|
276
1217
|
}
|
|
277
1218
|
async listFiles(options) {
|
|
1219
|
+
const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
|
|
278
1220
|
const jwt = await this.generateJWT(this.id, {
|
|
279
1221
|
permissions: ["git:read"],
|
|
280
|
-
ttl
|
|
281
|
-
// 1hr in seconds
|
|
1222
|
+
ttl
|
|
282
1223
|
});
|
|
283
1224
|
const params = options?.ref ? { ref: options.ref } : void 0;
|
|
284
1225
|
const response = await this.api.get({ path: "repos/files", params }, jwt);
|
|
285
|
-
|
|
1226
|
+
const raw = listFilesResponseSchema.parse(await response.json());
|
|
1227
|
+
return { paths: raw.paths, ref: raw.ref };
|
|
286
1228
|
}
|
|
287
1229
|
async listBranches(options) {
|
|
1230
|
+
const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
|
|
288
1231
|
const jwt = await this.generateJWT(this.id, {
|
|
289
1232
|
permissions: ["git:read"],
|
|
290
|
-
ttl
|
|
291
|
-
// 1hr in seconds
|
|
1233
|
+
ttl
|
|
292
1234
|
});
|
|
1235
|
+
const cursor = options?.cursor;
|
|
1236
|
+
const limit = options?.limit;
|
|
293
1237
|
let params;
|
|
294
|
-
if (
|
|
1238
|
+
if (typeof cursor === "string" || typeof limit === "number") {
|
|
295
1239
|
params = {};
|
|
296
|
-
if (
|
|
297
|
-
params.cursor =
|
|
1240
|
+
if (typeof cursor === "string") {
|
|
1241
|
+
params.cursor = cursor;
|
|
298
1242
|
}
|
|
299
|
-
if (typeof
|
|
300
|
-
params.limit =
|
|
1243
|
+
if (typeof limit === "number") {
|
|
1244
|
+
params.limit = limit.toString();
|
|
301
1245
|
}
|
|
302
1246
|
}
|
|
303
1247
|
const response = await this.api.get({ path: "repos/branches", params }, jwt);
|
|
304
|
-
|
|
1248
|
+
const raw = listBranchesResponseSchema.parse(await response.json());
|
|
1249
|
+
return transformListBranchesResult({
|
|
1250
|
+
...raw,
|
|
1251
|
+
next_cursor: raw.next_cursor ?? void 0
|
|
1252
|
+
});
|
|
305
1253
|
}
|
|
306
1254
|
async listCommits(options) {
|
|
1255
|
+
const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
|
|
307
1256
|
const jwt = await this.generateJWT(this.id, {
|
|
308
1257
|
permissions: ["git:read"],
|
|
309
|
-
ttl
|
|
310
|
-
// 1hr in seconds
|
|
1258
|
+
ttl
|
|
311
1259
|
});
|
|
312
1260
|
let params;
|
|
313
1261
|
if (options?.branch || options?.cursor || options?.limit) {
|
|
@@ -323,13 +1271,17 @@ var RepoImpl = class {
|
|
|
323
1271
|
}
|
|
324
1272
|
}
|
|
325
1273
|
const response = await this.api.get({ path: "repos/commits", params }, jwt);
|
|
326
|
-
|
|
1274
|
+
const raw = listCommitsResponseSchema.parse(await response.json());
|
|
1275
|
+
return transformListCommitsResult({
|
|
1276
|
+
...raw,
|
|
1277
|
+
next_cursor: raw.next_cursor ?? void 0
|
|
1278
|
+
});
|
|
327
1279
|
}
|
|
328
1280
|
async getBranchDiff(options) {
|
|
1281
|
+
const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
|
|
329
1282
|
const jwt = await this.generateJWT(this.id, {
|
|
330
1283
|
permissions: ["git:read"],
|
|
331
|
-
ttl
|
|
332
|
-
// 1hr in seconds
|
|
1284
|
+
ttl
|
|
333
1285
|
});
|
|
334
1286
|
const params = {
|
|
335
1287
|
branch: options.branch
|
|
@@ -338,38 +1290,27 @@ var RepoImpl = class {
|
|
|
338
1290
|
params.base = options.base;
|
|
339
1291
|
}
|
|
340
1292
|
const response = await this.api.get({ path: "repos/branches/diff", params }, jwt);
|
|
341
|
-
|
|
1293
|
+
const raw = branchDiffResponseSchema.parse(await response.json());
|
|
1294
|
+
return transformBranchDiffResult(raw);
|
|
342
1295
|
}
|
|
343
1296
|
async getCommitDiff(options) {
|
|
1297
|
+
const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
|
|
344
1298
|
const jwt = await this.generateJWT(this.id, {
|
|
345
1299
|
permissions: ["git:read"],
|
|
346
|
-
ttl
|
|
347
|
-
// 1hr in seconds
|
|
1300
|
+
ttl
|
|
348
1301
|
});
|
|
349
1302
|
const params = {
|
|
350
1303
|
sha: options.sha
|
|
351
1304
|
};
|
|
352
1305
|
const response = await this.api.get({ path: "repos/diff", params }, jwt);
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
async getCommit(options) {
|
|
356
|
-
const jwt = await this.generateJWT(this.id, {
|
|
357
|
-
permissions: ["git:read"],
|
|
358
|
-
ttl: options?.ttl ?? 1 * 60 * 60
|
|
359
|
-
// 1hr in seconds
|
|
360
|
-
});
|
|
361
|
-
const params = {
|
|
362
|
-
repo: this.id,
|
|
363
|
-
sha: options.sha
|
|
364
|
-
};
|
|
365
|
-
const response = await this.api.get({ path: "commit", params }, jwt);
|
|
366
|
-
return await response.json();
|
|
1306
|
+
const raw = commitDiffResponseSchema.parse(await response.json());
|
|
1307
|
+
return transformCommitDiffResult(raw);
|
|
367
1308
|
}
|
|
368
1309
|
async pullUpstream(options) {
|
|
1310
|
+
const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
|
|
369
1311
|
const jwt = await this.generateJWT(this.id, {
|
|
370
1312
|
permissions: ["git:write"],
|
|
371
|
-
ttl
|
|
372
|
-
// 1hr in seconds
|
|
1313
|
+
ttl
|
|
373
1314
|
});
|
|
374
1315
|
const body = {};
|
|
375
1316
|
if (options.ref) {
|
|
@@ -381,6 +1322,90 @@ var RepoImpl = class {
|
|
|
381
1322
|
}
|
|
382
1323
|
return;
|
|
383
1324
|
}
|
|
1325
|
+
async resetCommit(options) {
|
|
1326
|
+
const targetBranch = options?.targetBranch?.trim();
|
|
1327
|
+
if (!targetBranch) {
|
|
1328
|
+
throw new Error("resetCommit targetBranch is required");
|
|
1329
|
+
}
|
|
1330
|
+
if (targetBranch.startsWith("refs/")) {
|
|
1331
|
+
throw new Error("resetCommit targetBranch must not include refs/ prefix");
|
|
1332
|
+
}
|
|
1333
|
+
const targetCommitSha = options?.targetCommitSha?.trim();
|
|
1334
|
+
if (!targetCommitSha) {
|
|
1335
|
+
throw new Error("resetCommit targetCommitSha is required");
|
|
1336
|
+
}
|
|
1337
|
+
const commitMessage = options?.commitMessage?.trim();
|
|
1338
|
+
const authorName = options.author?.name?.trim();
|
|
1339
|
+
const authorEmail = options.author?.email?.trim();
|
|
1340
|
+
if (!authorName || !authorEmail) {
|
|
1341
|
+
throw new Error("resetCommit author name and email are required");
|
|
1342
|
+
}
|
|
1343
|
+
const ttl = resolveCommitTtlSeconds(options);
|
|
1344
|
+
const jwt = await this.generateJWT(this.id, {
|
|
1345
|
+
permissions: ["git:write"],
|
|
1346
|
+
ttl
|
|
1347
|
+
});
|
|
1348
|
+
const metadata = {
|
|
1349
|
+
target_branch: targetBranch,
|
|
1350
|
+
target_commit_sha: targetCommitSha,
|
|
1351
|
+
author: {
|
|
1352
|
+
name: authorName,
|
|
1353
|
+
email: authorEmail
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
if (commitMessage) {
|
|
1357
|
+
metadata.commit_message = commitMessage;
|
|
1358
|
+
}
|
|
1359
|
+
const expectedHeadSha = options.expectedHeadSha?.trim();
|
|
1360
|
+
if (expectedHeadSha) {
|
|
1361
|
+
metadata.expected_head_sha = expectedHeadSha;
|
|
1362
|
+
}
|
|
1363
|
+
if (options.committer) {
|
|
1364
|
+
const committerName = options.committer.name?.trim();
|
|
1365
|
+
const committerEmail = options.committer.email?.trim();
|
|
1366
|
+
if (!committerName || !committerEmail) {
|
|
1367
|
+
throw new Error("resetCommit committer name and email are required when provided");
|
|
1368
|
+
}
|
|
1369
|
+
metadata.committer = {
|
|
1370
|
+
name: committerName,
|
|
1371
|
+
email: committerEmail
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
const response = await this.api.post({ path: "repos/reset-commits", body: { metadata } }, jwt, {
|
|
1375
|
+
allowedStatus: [...RESET_COMMIT_ALLOWED_STATUS]
|
|
1376
|
+
});
|
|
1377
|
+
const payload = await response.json();
|
|
1378
|
+
const parsed = parseResetCommitPayload(payload);
|
|
1379
|
+
if (parsed && "ack" in parsed) {
|
|
1380
|
+
return buildResetCommitResult(parsed.ack);
|
|
1381
|
+
}
|
|
1382
|
+
const failure = parsed && "failure" in parsed ? parsed.failure : void 0;
|
|
1383
|
+
const status = failure?.status ?? httpStatusToResetStatus(response.status);
|
|
1384
|
+
const message = failure?.message ?? `Reset commit failed with HTTP ${response.status}` + (response.statusText ? ` ${response.statusText}` : "");
|
|
1385
|
+
throw new RefUpdateError(message, {
|
|
1386
|
+
status,
|
|
1387
|
+
refUpdate: failure?.refUpdate
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
createCommit(options) {
|
|
1391
|
+
const version = this.options.apiVersion ?? API_VERSION;
|
|
1392
|
+
const baseUrl = this.options.apiBaseUrl ?? API_BASE_URL;
|
|
1393
|
+
const transport = new FetchCommitTransport({ baseUrl, version });
|
|
1394
|
+
const ttl = resolveCommitTtlSeconds(options);
|
|
1395
|
+
const builderOptions = {
|
|
1396
|
+
...options,
|
|
1397
|
+
ttl
|
|
1398
|
+
};
|
|
1399
|
+
const getAuthToken = () => this.generateJWT(this.id, {
|
|
1400
|
+
permissions: ["git:write"],
|
|
1401
|
+
ttl
|
|
1402
|
+
});
|
|
1403
|
+
return createCommitBuilder({
|
|
1404
|
+
options: builderOptions,
|
|
1405
|
+
getAuthToken,
|
|
1406
|
+
transport
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
384
1409
|
};
|
|
385
1410
|
var GitStorage = class _GitStorage {
|
|
386
1411
|
static overrides = {};
|
|
@@ -401,13 +1426,15 @@ var GitStorage = class _GitStorage {
|
|
|
401
1426
|
const resolvedApiBaseUrl = options.apiBaseUrl ?? _GitStorage.overrides.apiBaseUrl ?? API_BASE_URL;
|
|
402
1427
|
const resolvedApiVersion = options.apiVersion ?? _GitStorage.overrides.apiVersion ?? API_VERSION;
|
|
403
1428
|
const resolvedStorageBaseUrl = options.storageBaseUrl ?? _GitStorage.overrides.storageBaseUrl ?? STORAGE_BASE_URL;
|
|
1429
|
+
const resolvedDefaultTtl = options.defaultTTL ?? _GitStorage.overrides.defaultTTL;
|
|
404
1430
|
this.api = getApiInstance(resolvedApiBaseUrl, resolvedApiVersion);
|
|
405
1431
|
this.options = {
|
|
406
1432
|
key: options.key,
|
|
407
1433
|
name: options.name,
|
|
408
1434
|
apiBaseUrl: resolvedApiBaseUrl,
|
|
409
1435
|
apiVersion: resolvedApiVersion,
|
|
410
|
-
storageBaseUrl: resolvedStorageBaseUrl
|
|
1436
|
+
storageBaseUrl: resolvedStorageBaseUrl,
|
|
1437
|
+
defaultTTL: resolvedDefaultTtl
|
|
411
1438
|
};
|
|
412
1439
|
}
|
|
413
1440
|
static override(options) {
|
|
@@ -419,10 +1446,10 @@ var GitStorage = class _GitStorage {
|
|
|
419
1446
|
*/
|
|
420
1447
|
async createRepo(options) {
|
|
421
1448
|
const repoId = options?.id || crypto.randomUUID();
|
|
1449
|
+
const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
|
|
422
1450
|
const jwt = await this.generateJWT(repoId, {
|
|
423
1451
|
permissions: ["repo:write"],
|
|
424
|
-
ttl
|
|
425
|
-
// 1hr in seconds
|
|
1452
|
+
ttl
|
|
426
1453
|
});
|
|
427
1454
|
const baseRepoOptions = options?.baseRepo ? {
|
|
428
1455
|
provider: "github",
|
|
@@ -450,7 +1477,7 @@ var GitStorage = class _GitStorage {
|
|
|
450
1477
|
async findOne(options) {
|
|
451
1478
|
const jwt = await this.generateJWT(options.id, {
|
|
452
1479
|
permissions: ["git:read"],
|
|
453
|
-
ttl:
|
|
1480
|
+
ttl: DEFAULT_TOKEN_TTL_SECONDS
|
|
454
1481
|
});
|
|
455
1482
|
const resp = await this.api.get("repo", jwt, { allowedStatus: [404] });
|
|
456
1483
|
if (resp.status === 404) {
|
|
@@ -471,7 +1498,7 @@ var GitStorage = class _GitStorage {
|
|
|
471
1498
|
*/
|
|
472
1499
|
async generateJWT(repoId, options) {
|
|
473
1500
|
const permissions = options?.permissions || ["git:write", "git:read"];
|
|
474
|
-
const ttl = options
|
|
1501
|
+
const ttl = resolveInvocationTtlSeconds(options, this.options.defaultTTL ?? 365 * 24 * 60 * 60);
|
|
475
1502
|
const now = Math.floor(Date.now() / 1e3);
|
|
476
1503
|
const payload = {
|
|
477
1504
|
iss: this.options.name,
|
|
@@ -490,6 +1517,6 @@ function createClient(options) {
|
|
|
490
1517
|
return new GitStorage(options);
|
|
491
1518
|
}
|
|
492
1519
|
|
|
493
|
-
export { GitStorage, createClient, parseSignatureHeader, validateWebhook, validateWebhookSignature };
|
|
1520
|
+
export { ApiError, GitStorage, RefUpdateError, createClient, parseSignatureHeader, validateWebhook, validateWebhookSignature };
|
|
494
1521
|
//# sourceMappingURL=index.js.map
|
|
495
1522
|
//# sourceMappingURL=index.js.map
|