@pierre/storage 0.0.11 → 0.1.1
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 +159 -78
- package/dist/index.cjs +739 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +560 -88
- package/dist/index.d.ts +560 -88
- package/dist/index.js +738 -73
- package/dist/index.js.map +1 -1
- package/package.json +39 -37
- package/src/commit.ts +196 -43
- package/src/errors.ts +50 -0
- package/src/fetch.ts +75 -5
- package/src/index.ts +389 -47
- package/src/schemas.ts +138 -0
- package/src/types.ts +182 -89
- package/src/util.ts +0 -18
- package/src/webhook.ts +75 -3
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var jose = require('jose');
|
|
4
4
|
var snakecaseKeys = require('snakecase-keys');
|
|
5
|
+
var zod = require('zod');
|
|
5
6
|
|
|
6
7
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
8
|
|
|
@@ -9,6 +10,150 @@ var snakecaseKeys__default = /*#__PURE__*/_interopDefault(snakecaseKeys);
|
|
|
9
10
|
|
|
10
11
|
// src/index.ts
|
|
11
12
|
|
|
13
|
+
// src/errors.ts
|
|
14
|
+
var RefUpdateError = class extends Error {
|
|
15
|
+
status;
|
|
16
|
+
reason;
|
|
17
|
+
refUpdate;
|
|
18
|
+
constructor(message, options) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "RefUpdateError";
|
|
21
|
+
this.status = options.status;
|
|
22
|
+
this.reason = options.reason ?? inferRefUpdateReason(options.status);
|
|
23
|
+
this.refUpdate = options.refUpdate;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var REF_REASON_MAP = {
|
|
27
|
+
precondition_failed: "precondition_failed",
|
|
28
|
+
conflict: "conflict",
|
|
29
|
+
not_found: "not_found",
|
|
30
|
+
invalid: "invalid",
|
|
31
|
+
timeout: "timeout",
|
|
32
|
+
unauthorized: "unauthorized",
|
|
33
|
+
forbidden: "forbidden",
|
|
34
|
+
unavailable: "unavailable",
|
|
35
|
+
internal: "internal",
|
|
36
|
+
failed: "failed",
|
|
37
|
+
ok: "unknown"
|
|
38
|
+
};
|
|
39
|
+
function inferRefUpdateReason(status) {
|
|
40
|
+
if (!status) {
|
|
41
|
+
return "unknown";
|
|
42
|
+
}
|
|
43
|
+
const trimmed = status.trim();
|
|
44
|
+
if (trimmed === "") {
|
|
45
|
+
return "unknown";
|
|
46
|
+
}
|
|
47
|
+
const label = trimmed.toLowerCase();
|
|
48
|
+
return REF_REASON_MAP[label] ?? "unknown";
|
|
49
|
+
}
|
|
50
|
+
var listFilesResponseSchema = zod.z.object({
|
|
51
|
+
paths: zod.z.array(zod.z.string()),
|
|
52
|
+
ref: zod.z.string()
|
|
53
|
+
});
|
|
54
|
+
var branchInfoSchema = zod.z.object({
|
|
55
|
+
cursor: zod.z.string(),
|
|
56
|
+
name: zod.z.string(),
|
|
57
|
+
head_sha: zod.z.string(),
|
|
58
|
+
created_at: zod.z.string()
|
|
59
|
+
});
|
|
60
|
+
var listBranchesResponseSchema = zod.z.object({
|
|
61
|
+
branches: zod.z.array(branchInfoSchema),
|
|
62
|
+
next_cursor: zod.z.string().nullable().optional(),
|
|
63
|
+
has_more: zod.z.boolean()
|
|
64
|
+
});
|
|
65
|
+
var commitInfoRawSchema = zod.z.object({
|
|
66
|
+
sha: zod.z.string(),
|
|
67
|
+
message: zod.z.string(),
|
|
68
|
+
author_name: zod.z.string(),
|
|
69
|
+
author_email: zod.z.string(),
|
|
70
|
+
committer_name: zod.z.string(),
|
|
71
|
+
committer_email: zod.z.string(),
|
|
72
|
+
date: zod.z.string()
|
|
73
|
+
});
|
|
74
|
+
var listCommitsResponseSchema = zod.z.object({
|
|
75
|
+
commits: zod.z.array(commitInfoRawSchema),
|
|
76
|
+
next_cursor: zod.z.string().nullable().optional(),
|
|
77
|
+
has_more: zod.z.boolean()
|
|
78
|
+
});
|
|
79
|
+
var diffStatsSchema = zod.z.object({
|
|
80
|
+
files: zod.z.number(),
|
|
81
|
+
additions: zod.z.number(),
|
|
82
|
+
deletions: zod.z.number(),
|
|
83
|
+
changes: zod.z.number()
|
|
84
|
+
});
|
|
85
|
+
var diffFileRawSchema = zod.z.object({
|
|
86
|
+
path: zod.z.string(),
|
|
87
|
+
state: zod.z.string(),
|
|
88
|
+
old_path: zod.z.string().nullable().optional(),
|
|
89
|
+
raw: zod.z.string(),
|
|
90
|
+
bytes: zod.z.number(),
|
|
91
|
+
is_eof: zod.z.boolean()
|
|
92
|
+
});
|
|
93
|
+
var filteredFileRawSchema = zod.z.object({
|
|
94
|
+
path: zod.z.string(),
|
|
95
|
+
state: zod.z.string(),
|
|
96
|
+
old_path: zod.z.string().nullable().optional(),
|
|
97
|
+
bytes: zod.z.number(),
|
|
98
|
+
is_eof: zod.z.boolean()
|
|
99
|
+
});
|
|
100
|
+
var branchDiffResponseSchema = zod.z.object({
|
|
101
|
+
branch: zod.z.string(),
|
|
102
|
+
base: zod.z.string(),
|
|
103
|
+
stats: diffStatsSchema,
|
|
104
|
+
files: zod.z.array(diffFileRawSchema),
|
|
105
|
+
filtered_files: zod.z.array(filteredFileRawSchema)
|
|
106
|
+
});
|
|
107
|
+
var commitDiffResponseSchema = zod.z.object({
|
|
108
|
+
sha: zod.z.string(),
|
|
109
|
+
stats: diffStatsSchema,
|
|
110
|
+
files: zod.z.array(diffFileRawSchema),
|
|
111
|
+
filtered_files: zod.z.array(filteredFileRawSchema)
|
|
112
|
+
});
|
|
113
|
+
var refUpdateResultSchema = zod.z.object({
|
|
114
|
+
branch: zod.z.string(),
|
|
115
|
+
old_sha: zod.z.string(),
|
|
116
|
+
new_sha: zod.z.string(),
|
|
117
|
+
success: zod.z.boolean(),
|
|
118
|
+
status: zod.z.string(),
|
|
119
|
+
message: zod.z.string().optional()
|
|
120
|
+
});
|
|
121
|
+
var commitPackCommitSchema = zod.z.object({
|
|
122
|
+
commit_sha: zod.z.string(),
|
|
123
|
+
tree_sha: zod.z.string(),
|
|
124
|
+
target_branch: zod.z.string(),
|
|
125
|
+
pack_bytes: zod.z.number(),
|
|
126
|
+
blob_count: zod.z.number()
|
|
127
|
+
});
|
|
128
|
+
var restoreCommitCommitSchema = commitPackCommitSchema.omit({ blob_count: true });
|
|
129
|
+
var refUpdateResultWithOptionalsSchema = zod.z.object({
|
|
130
|
+
branch: zod.z.string().optional(),
|
|
131
|
+
old_sha: zod.z.string().optional(),
|
|
132
|
+
new_sha: zod.z.string().optional(),
|
|
133
|
+
success: zod.z.boolean().optional(),
|
|
134
|
+
status: zod.z.string(),
|
|
135
|
+
message: zod.z.string().optional()
|
|
136
|
+
});
|
|
137
|
+
var commitPackAckSchema = zod.z.object({
|
|
138
|
+
commit: commitPackCommitSchema,
|
|
139
|
+
result: refUpdateResultSchema
|
|
140
|
+
});
|
|
141
|
+
var restoreCommitAckSchema = zod.z.object({
|
|
142
|
+
commit: restoreCommitCommitSchema,
|
|
143
|
+
result: refUpdateResultSchema.extend({ success: zod.z.literal(true) })
|
|
144
|
+
});
|
|
145
|
+
var commitPackResponseSchema = zod.z.object({
|
|
146
|
+
commit: commitPackCommitSchema.partial().optional().nullable(),
|
|
147
|
+
result: refUpdateResultWithOptionalsSchema
|
|
148
|
+
});
|
|
149
|
+
var restoreCommitResponseSchema = zod.z.object({
|
|
150
|
+
commit: restoreCommitCommitSchema.partial().optional().nullable(),
|
|
151
|
+
result: refUpdateResultWithOptionalsSchema
|
|
152
|
+
});
|
|
153
|
+
var errorEnvelopeSchema = zod.z.object({
|
|
154
|
+
error: zod.z.string()
|
|
155
|
+
});
|
|
156
|
+
|
|
12
157
|
// src/commit.ts
|
|
13
158
|
var MAX_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
14
159
|
var DEFAULT_TTL_SECONDS = 60 * 60;
|
|
@@ -23,18 +168,30 @@ var CommitBuilderImpl = class {
|
|
|
23
168
|
this.options = { ...deps.options };
|
|
24
169
|
this.getAuthToken = deps.getAuthToken;
|
|
25
170
|
this.transport = deps.transport;
|
|
26
|
-
const trimmedTarget = this.options.
|
|
171
|
+
const trimmedTarget = this.options.targetBranch?.trim();
|
|
27
172
|
const trimmedMessage = this.options.commitMessage?.trim();
|
|
173
|
+
const trimmedAuthorName = this.options.author?.name?.trim();
|
|
174
|
+
const trimmedAuthorEmail = this.options.author?.email?.trim();
|
|
28
175
|
if (!trimmedTarget) {
|
|
29
|
-
throw new Error("createCommit
|
|
176
|
+
throw new Error("createCommit targetBranch is required");
|
|
177
|
+
}
|
|
178
|
+
if (trimmedTarget.startsWith("refs/")) {
|
|
179
|
+
throw new Error("createCommit targetBranch must not include refs/ prefix");
|
|
30
180
|
}
|
|
31
181
|
if (!trimmedMessage) {
|
|
32
182
|
throw new Error("createCommit commitMessage is required");
|
|
33
183
|
}
|
|
34
|
-
|
|
184
|
+
if (!trimmedAuthorName || !trimmedAuthorEmail) {
|
|
185
|
+
throw new Error("createCommit author name and email are required");
|
|
186
|
+
}
|
|
187
|
+
this.options.targetBranch = trimmedTarget;
|
|
35
188
|
this.options.commitMessage = trimmedMessage;
|
|
36
|
-
|
|
37
|
-
|
|
189
|
+
this.options.author = {
|
|
190
|
+
name: trimmedAuthorName,
|
|
191
|
+
email: trimmedAuthorEmail
|
|
192
|
+
};
|
|
193
|
+
if (typeof this.options.expectedHeadSha === "string") {
|
|
194
|
+
this.options.expectedHeadSha = this.options.expectedHeadSha.trim();
|
|
38
195
|
}
|
|
39
196
|
}
|
|
40
197
|
addFile(path, source, options) {
|
|
@@ -52,11 +209,21 @@ var CommitBuilderImpl = class {
|
|
|
52
209
|
return this;
|
|
53
210
|
}
|
|
54
211
|
addFileFromString(path, contents, options) {
|
|
55
|
-
const encoding = options?.encoding;
|
|
56
|
-
|
|
57
|
-
|
|
212
|
+
const encoding = options?.encoding ?? "utf8";
|
|
213
|
+
const normalizedEncoding = encoding === "utf-8" ? "utf8" : encoding;
|
|
214
|
+
let data;
|
|
215
|
+
if (normalizedEncoding === "utf8") {
|
|
216
|
+
data = new TextEncoder().encode(contents);
|
|
217
|
+
} else if (BufferCtor) {
|
|
218
|
+
data = BufferCtor.from(
|
|
219
|
+
contents,
|
|
220
|
+
normalizedEncoding
|
|
221
|
+
);
|
|
222
|
+
} else {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`Unsupported encoding "${encoding}" in this environment. Non-UTF encodings require Node.js Buffer support.`
|
|
225
|
+
);
|
|
58
226
|
}
|
|
59
|
-
const data = new TextEncoder().encode(contents);
|
|
60
227
|
return this.addFile(path, data, options);
|
|
61
228
|
}
|
|
62
229
|
deletePath(path) {
|
|
@@ -78,12 +245,13 @@ var CommitBuilderImpl = class {
|
|
|
78
245
|
chunks: chunkify(op.streamFactory())
|
|
79
246
|
}));
|
|
80
247
|
const authorization = await this.getAuthToken();
|
|
81
|
-
|
|
248
|
+
const ack = await this.transport.send({
|
|
82
249
|
authorization,
|
|
83
250
|
signal: this.options.signal,
|
|
84
251
|
metadata,
|
|
85
252
|
blobs: blobEntries
|
|
86
253
|
});
|
|
254
|
+
return buildCommitResult(ack);
|
|
87
255
|
}
|
|
88
256
|
buildMetadata() {
|
|
89
257
|
const files = this.operations.map((op) => {
|
|
@@ -98,18 +266,22 @@ var CommitBuilderImpl = class {
|
|
|
98
266
|
return entry;
|
|
99
267
|
});
|
|
100
268
|
const metadata = {
|
|
101
|
-
|
|
269
|
+
target_branch: this.options.targetBranch,
|
|
102
270
|
commit_message: this.options.commitMessage,
|
|
271
|
+
author: {
|
|
272
|
+
name: this.options.author.name,
|
|
273
|
+
email: this.options.author.email
|
|
274
|
+
},
|
|
103
275
|
files
|
|
104
276
|
};
|
|
105
|
-
if (this.options.
|
|
106
|
-
metadata.
|
|
107
|
-
}
|
|
108
|
-
if (this.options.author) {
|
|
109
|
-
metadata.author = { ...this.options.author };
|
|
277
|
+
if (this.options.expectedHeadSha) {
|
|
278
|
+
metadata.expected_head_sha = this.options.expectedHeadSha;
|
|
110
279
|
}
|
|
111
280
|
if (this.options.committer) {
|
|
112
|
-
metadata.committer = {
|
|
281
|
+
metadata.committer = {
|
|
282
|
+
name: this.options.committer.name,
|
|
283
|
+
email: this.options.committer.email
|
|
284
|
+
};
|
|
113
285
|
}
|
|
114
286
|
return metadata;
|
|
115
287
|
}
|
|
@@ -145,10 +317,15 @@ var FetchCommitTransport = class {
|
|
|
145
317
|
signal: request.signal
|
|
146
318
|
});
|
|
147
319
|
if (!response.ok) {
|
|
148
|
-
const
|
|
149
|
-
throw new
|
|
320
|
+
const { statusMessage, statusLabel, refUpdate } = await parseCommitPackError(response);
|
|
321
|
+
throw new RefUpdateError(statusMessage, {
|
|
322
|
+
status: statusLabel,
|
|
323
|
+
message: statusMessage,
|
|
324
|
+
refUpdate
|
|
325
|
+
});
|
|
150
326
|
}
|
|
151
|
-
|
|
327
|
+
const ack = commitPackAckSchema.parse(await response.json());
|
|
328
|
+
return ack;
|
|
152
329
|
}
|
|
153
330
|
};
|
|
154
331
|
function toRequestBody(iterable) {
|
|
@@ -195,6 +372,34 @@ function buildMessageIterable(metadata, blobs) {
|
|
|
195
372
|
}
|
|
196
373
|
};
|
|
197
374
|
}
|
|
375
|
+
function buildCommitResult(ack) {
|
|
376
|
+
const refUpdate = toRefUpdate(ack.result);
|
|
377
|
+
if (!ack.result.success) {
|
|
378
|
+
throw new RefUpdateError(
|
|
379
|
+
ack.result.message ?? `Commit failed with status ${ack.result.status}`,
|
|
380
|
+
{
|
|
381
|
+
status: ack.result.status,
|
|
382
|
+
message: ack.result.message,
|
|
383
|
+
refUpdate
|
|
384
|
+
}
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
commitSha: ack.commit.commit_sha,
|
|
389
|
+
treeSha: ack.commit.tree_sha,
|
|
390
|
+
targetBranch: ack.commit.target_branch,
|
|
391
|
+
packBytes: ack.commit.pack_bytes,
|
|
392
|
+
blobCount: ack.commit.blob_count,
|
|
393
|
+
refUpdate
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function toRefUpdate(result) {
|
|
397
|
+
return {
|
|
398
|
+
branch: result.branch,
|
|
399
|
+
oldSha: result.old_sha,
|
|
400
|
+
newSha: result.new_sha
|
|
401
|
+
};
|
|
402
|
+
}
|
|
198
403
|
async function* chunkify(source) {
|
|
199
404
|
let pending = null;
|
|
200
405
|
let produced = false;
|
|
@@ -254,6 +459,10 @@ async function* toAsyncIterable(source) {
|
|
|
254
459
|
return;
|
|
255
460
|
}
|
|
256
461
|
}
|
|
462
|
+
if (isReadableStreamLike(source)) {
|
|
463
|
+
yield* readReadableStream(source);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
257
466
|
if (isAsyncIterable(source)) {
|
|
258
467
|
for await (const chunk of source) {
|
|
259
468
|
yield ensureUint8Array(chunk);
|
|
@@ -352,15 +561,104 @@ function createCommitBuilder(deps) {
|
|
|
352
561
|
return new CommitBuilderImpl(deps);
|
|
353
562
|
}
|
|
354
563
|
function resolveCommitTtlSeconds(options) {
|
|
355
|
-
|
|
564
|
+
if (typeof options?.ttl === "number" && options.ttl > 0) {
|
|
565
|
+
return options.ttl;
|
|
566
|
+
}
|
|
567
|
+
return DEFAULT_TTL_SECONDS;
|
|
568
|
+
}
|
|
569
|
+
async function parseCommitPackError(response) {
|
|
570
|
+
const fallbackMessage = `createCommit request failed (${response.status} ${response.statusText})`;
|
|
571
|
+
const cloned = response.clone();
|
|
572
|
+
let jsonBody;
|
|
573
|
+
try {
|
|
574
|
+
jsonBody = await cloned.json();
|
|
575
|
+
} catch {
|
|
576
|
+
jsonBody = void 0;
|
|
577
|
+
}
|
|
578
|
+
let textBody;
|
|
579
|
+
if (jsonBody === void 0) {
|
|
580
|
+
try {
|
|
581
|
+
textBody = await response.text();
|
|
582
|
+
} catch {
|
|
583
|
+
textBody = void 0;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
const defaultStatus = (() => {
|
|
587
|
+
const inferred = inferRefUpdateReason(String(response.status));
|
|
588
|
+
return inferred === "unknown" ? "failed" : inferred;
|
|
589
|
+
})();
|
|
590
|
+
let statusLabel = defaultStatus;
|
|
591
|
+
let refUpdate;
|
|
592
|
+
let message;
|
|
593
|
+
if (jsonBody !== void 0) {
|
|
594
|
+
const parsedResponse = commitPackResponseSchema.safeParse(jsonBody);
|
|
595
|
+
if (parsedResponse.success) {
|
|
596
|
+
const result = parsedResponse.data.result;
|
|
597
|
+
if (typeof result.status === "string" && result.status.trim() !== "") {
|
|
598
|
+
statusLabel = result.status.trim();
|
|
599
|
+
}
|
|
600
|
+
refUpdate = toPartialRefUpdateFields(result.branch, result.old_sha, result.new_sha);
|
|
601
|
+
if (typeof result.message === "string" && result.message.trim() !== "") {
|
|
602
|
+
message = result.message.trim();
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (!message) {
|
|
606
|
+
const parsedError = errorEnvelopeSchema.safeParse(jsonBody);
|
|
607
|
+
if (parsedError.success) {
|
|
608
|
+
const trimmed = parsedError.data.error.trim();
|
|
609
|
+
if (trimmed) {
|
|
610
|
+
message = trimmed;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
if (!message && typeof jsonBody === "string" && jsonBody.trim() !== "") {
|
|
616
|
+
message = jsonBody.trim();
|
|
617
|
+
}
|
|
618
|
+
if (!message && textBody && textBody.trim() !== "") {
|
|
619
|
+
message = textBody.trim();
|
|
620
|
+
}
|
|
621
|
+
return {
|
|
622
|
+
statusMessage: message ?? fallbackMessage,
|
|
623
|
+
statusLabel,
|
|
624
|
+
refUpdate
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
function toPartialRefUpdateFields(branch, oldSha, newSha) {
|
|
628
|
+
const refUpdate = {};
|
|
629
|
+
if (typeof branch === "string" && branch.trim() !== "") {
|
|
630
|
+
refUpdate.branch = branch.trim();
|
|
631
|
+
}
|
|
632
|
+
if (typeof oldSha === "string" && oldSha.trim() !== "") {
|
|
633
|
+
refUpdate.oldSha = oldSha.trim();
|
|
634
|
+
}
|
|
635
|
+
if (typeof newSha === "string" && newSha.trim() !== "") {
|
|
636
|
+
refUpdate.newSha = newSha.trim();
|
|
637
|
+
}
|
|
638
|
+
return Object.keys(refUpdate).length > 0 ? refUpdate : void 0;
|
|
356
639
|
}
|
|
357
640
|
|
|
358
641
|
// src/fetch.ts
|
|
642
|
+
var ApiError = class extends Error {
|
|
643
|
+
status;
|
|
644
|
+
statusText;
|
|
645
|
+
method;
|
|
646
|
+
url;
|
|
647
|
+
body;
|
|
648
|
+
constructor(params) {
|
|
649
|
+
super(params.message);
|
|
650
|
+
this.name = "ApiError";
|
|
651
|
+
this.status = params.status;
|
|
652
|
+
this.statusText = params.statusText;
|
|
653
|
+
this.method = params.method;
|
|
654
|
+
this.url = params.url;
|
|
655
|
+
this.body = params.body;
|
|
656
|
+
}
|
|
657
|
+
};
|
|
359
658
|
var ApiFetcher = class {
|
|
360
659
|
constructor(API_BASE_URL2, version) {
|
|
361
660
|
this.API_BASE_URL = API_BASE_URL2;
|
|
362
661
|
this.version = version;
|
|
363
|
-
console.log("api fetcher created", API_BASE_URL2, version);
|
|
364
662
|
}
|
|
365
663
|
getBaseUrl() {
|
|
366
664
|
return `${this.API_BASE_URL}/api/v${this.version}`;
|
|
@@ -390,9 +688,48 @@ var ApiFetcher = class {
|
|
|
390
688
|
const response = await fetch(requestUrl, requestOptions);
|
|
391
689
|
if (!response.ok) {
|
|
392
690
|
const allowed = options?.allowedStatus ?? [];
|
|
393
|
-
if (
|
|
394
|
-
|
|
691
|
+
if (allowed.includes(response.status)) {
|
|
692
|
+
return response;
|
|
693
|
+
}
|
|
694
|
+
let errorBody;
|
|
695
|
+
let message;
|
|
696
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
697
|
+
try {
|
|
698
|
+
if (contentType.includes("application/json")) {
|
|
699
|
+
errorBody = await response.json();
|
|
700
|
+
} else {
|
|
701
|
+
const text = await response.text();
|
|
702
|
+
errorBody = text;
|
|
703
|
+
}
|
|
704
|
+
} catch {
|
|
705
|
+
try {
|
|
706
|
+
errorBody = await response.text();
|
|
707
|
+
} catch {
|
|
708
|
+
errorBody = void 0;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (typeof errorBody === "string") {
|
|
712
|
+
const trimmed = errorBody.trim();
|
|
713
|
+
if (trimmed) {
|
|
714
|
+
message = trimmed;
|
|
715
|
+
}
|
|
716
|
+
} else if (errorBody && typeof errorBody === "object") {
|
|
717
|
+
const parsedError = errorEnvelopeSchema.safeParse(errorBody);
|
|
718
|
+
if (parsedError.success) {
|
|
719
|
+
const trimmed = parsedError.data.error.trim();
|
|
720
|
+
if (trimmed) {
|
|
721
|
+
message = trimmed;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
395
724
|
}
|
|
725
|
+
throw new ApiError({
|
|
726
|
+
message: message ?? `Request ${method} ${requestUrl} failed with status ${response.status} ${response.statusText}`,
|
|
727
|
+
status: response.status,
|
|
728
|
+
statusText: response.statusText,
|
|
729
|
+
method,
|
|
730
|
+
url: requestUrl,
|
|
731
|
+
body: errorBody
|
|
732
|
+
});
|
|
396
733
|
}
|
|
397
734
|
return response;
|
|
398
735
|
}
|
|
@@ -565,9 +902,9 @@ async function validateWebhook(payload, headers, secret, options = {}) {
|
|
|
565
902
|
return validationResult;
|
|
566
903
|
}
|
|
567
904
|
const payloadStr = typeof payload === "string" ? payload : payload.toString("utf8");
|
|
568
|
-
let
|
|
905
|
+
let parsedJson;
|
|
569
906
|
try {
|
|
570
|
-
|
|
907
|
+
parsedJson = JSON.parse(payloadStr);
|
|
571
908
|
} catch {
|
|
572
909
|
return {
|
|
573
910
|
valid: false,
|
|
@@ -575,25 +912,283 @@ async function validateWebhook(payload, headers, secret, options = {}) {
|
|
|
575
912
|
timestamp: validationResult.timestamp
|
|
576
913
|
};
|
|
577
914
|
}
|
|
915
|
+
const conversion = convertWebhookPayload(String(eventType), parsedJson);
|
|
916
|
+
if (!conversion.valid) {
|
|
917
|
+
return {
|
|
918
|
+
valid: false,
|
|
919
|
+
error: conversion.error,
|
|
920
|
+
timestamp: validationResult.timestamp
|
|
921
|
+
};
|
|
922
|
+
}
|
|
578
923
|
return {
|
|
579
924
|
valid: true,
|
|
580
925
|
eventType,
|
|
581
926
|
timestamp: validationResult.timestamp,
|
|
582
|
-
payload:
|
|
927
|
+
payload: conversion.payload
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
function convertWebhookPayload(eventType, raw) {
|
|
931
|
+
if (eventType === "push") {
|
|
932
|
+
if (!isRawWebhookPushEvent(raw)) {
|
|
933
|
+
return {
|
|
934
|
+
valid: false,
|
|
935
|
+
error: "Invalid push payload"
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
return {
|
|
939
|
+
valid: true,
|
|
940
|
+
payload: transformPushEvent(raw)
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
const fallbackPayload = { type: eventType, raw };
|
|
944
|
+
return {
|
|
945
|
+
valid: true,
|
|
946
|
+
payload: fallbackPayload
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
function transformPushEvent(raw) {
|
|
950
|
+
return {
|
|
951
|
+
type: "push",
|
|
952
|
+
repository: {
|
|
953
|
+
id: raw.repository.id,
|
|
954
|
+
url: raw.repository.url
|
|
955
|
+
},
|
|
956
|
+
ref: raw.ref,
|
|
957
|
+
before: raw.before,
|
|
958
|
+
after: raw.after,
|
|
959
|
+
customerId: raw.customer_id,
|
|
960
|
+
pushedAt: new Date(raw.pushed_at),
|
|
961
|
+
rawPushedAt: raw.pushed_at
|
|
583
962
|
};
|
|
584
963
|
}
|
|
964
|
+
function isRawWebhookPushEvent(value) {
|
|
965
|
+
if (!isRecord(value)) {
|
|
966
|
+
return false;
|
|
967
|
+
}
|
|
968
|
+
if (!isRecord(value.repository)) {
|
|
969
|
+
return false;
|
|
970
|
+
}
|
|
971
|
+
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";
|
|
972
|
+
}
|
|
973
|
+
function isRecord(value) {
|
|
974
|
+
return typeof value === "object" && value !== null;
|
|
975
|
+
}
|
|
585
976
|
|
|
586
977
|
// src/index.ts
|
|
587
978
|
var API_BASE_URL = "https://api.code.storage";
|
|
588
979
|
var STORAGE_BASE_URL = "code.storage";
|
|
589
980
|
var API_VERSION = 1;
|
|
590
981
|
var apiInstanceMap = /* @__PURE__ */ new Map();
|
|
982
|
+
var DEFAULT_TOKEN_TTL_SECONDS = 60 * 60;
|
|
983
|
+
var RESTORE_COMMIT_ALLOWED_STATUS = [
|
|
984
|
+
400,
|
|
985
|
+
// Bad Request - validation errors
|
|
986
|
+
401,
|
|
987
|
+
// Unauthorized - missing/invalid auth header
|
|
988
|
+
403,
|
|
989
|
+
// Forbidden - missing git:write scope
|
|
990
|
+
404,
|
|
991
|
+
// Not Found - repo lookup failures
|
|
992
|
+
408,
|
|
993
|
+
// Request Timeout - client cancelled
|
|
994
|
+
409,
|
|
995
|
+
// Conflict - concurrent ref updates
|
|
996
|
+
412,
|
|
997
|
+
// Precondition Failed - optimistic concurrency
|
|
998
|
+
422,
|
|
999
|
+
// Unprocessable Entity - metadata issues
|
|
1000
|
+
429,
|
|
1001
|
+
// Too Many Requests - upstream throttling
|
|
1002
|
+
499,
|
|
1003
|
+
// Client Closed Request - storage cancellation
|
|
1004
|
+
500,
|
|
1005
|
+
// Internal Server Error - generic failure
|
|
1006
|
+
502,
|
|
1007
|
+
// Bad Gateway - storage/gateway bridge issues
|
|
1008
|
+
503,
|
|
1009
|
+
// Service Unavailable - storage selection failures
|
|
1010
|
+
504
|
|
1011
|
+
// Gateway Timeout - long-running storage operations
|
|
1012
|
+
];
|
|
1013
|
+
function resolveInvocationTtlSeconds(options, defaultValue = DEFAULT_TOKEN_TTL_SECONDS) {
|
|
1014
|
+
if (typeof options?.ttl === "number" && options.ttl > 0) {
|
|
1015
|
+
return options.ttl;
|
|
1016
|
+
}
|
|
1017
|
+
return defaultValue;
|
|
1018
|
+
}
|
|
1019
|
+
function toRefUpdate2(result) {
|
|
1020
|
+
return {
|
|
1021
|
+
branch: result.branch,
|
|
1022
|
+
oldSha: result.old_sha,
|
|
1023
|
+
newSha: result.new_sha
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
function buildRestoreCommitResult(ack) {
|
|
1027
|
+
const refUpdate = toRefUpdate2(ack.result);
|
|
1028
|
+
if (!ack.result.success) {
|
|
1029
|
+
throw new RefUpdateError(
|
|
1030
|
+
ack.result.message ?? `Restore commit failed with status ${ack.result.status}`,
|
|
1031
|
+
{
|
|
1032
|
+
status: ack.result.status,
|
|
1033
|
+
message: ack.result.message,
|
|
1034
|
+
refUpdate
|
|
1035
|
+
}
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
return {
|
|
1039
|
+
commitSha: ack.commit.commit_sha,
|
|
1040
|
+
treeSha: ack.commit.tree_sha,
|
|
1041
|
+
targetBranch: ack.commit.target_branch,
|
|
1042
|
+
packBytes: ack.commit.pack_bytes,
|
|
1043
|
+
refUpdate
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
function toPartialRefUpdate(branch, oldSha, newSha) {
|
|
1047
|
+
const refUpdate = {};
|
|
1048
|
+
if (typeof branch === "string" && branch.trim() !== "") {
|
|
1049
|
+
refUpdate.branch = branch;
|
|
1050
|
+
}
|
|
1051
|
+
if (typeof oldSha === "string" && oldSha.trim() !== "") {
|
|
1052
|
+
refUpdate.oldSha = oldSha;
|
|
1053
|
+
}
|
|
1054
|
+
if (typeof newSha === "string" && newSha.trim() !== "") {
|
|
1055
|
+
refUpdate.newSha = newSha;
|
|
1056
|
+
}
|
|
1057
|
+
return Object.keys(refUpdate).length > 0 ? refUpdate : void 0;
|
|
1058
|
+
}
|
|
1059
|
+
function parseRestoreCommitPayload(payload) {
|
|
1060
|
+
const ack = restoreCommitAckSchema.safeParse(payload);
|
|
1061
|
+
if (ack.success) {
|
|
1062
|
+
return { ack: ack.data };
|
|
1063
|
+
}
|
|
1064
|
+
const failure = restoreCommitResponseSchema.safeParse(payload);
|
|
1065
|
+
if (failure.success) {
|
|
1066
|
+
const result = failure.data.result;
|
|
1067
|
+
return {
|
|
1068
|
+
failure: {
|
|
1069
|
+
status: result.status,
|
|
1070
|
+
message: result.message,
|
|
1071
|
+
refUpdate: toPartialRefUpdate(result.branch, result.old_sha, result.new_sha)
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
function httpStatusToRestoreStatus(status) {
|
|
1078
|
+
switch (status) {
|
|
1079
|
+
case 409:
|
|
1080
|
+
return "conflict";
|
|
1081
|
+
case 412:
|
|
1082
|
+
return "precondition_failed";
|
|
1083
|
+
default:
|
|
1084
|
+
return `${status}`;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
591
1087
|
function getApiInstance(baseUrl, version) {
|
|
592
1088
|
if (!apiInstanceMap.has(`${baseUrl}--${version}`)) {
|
|
593
1089
|
apiInstanceMap.set(`${baseUrl}--${version}`, new ApiFetcher(baseUrl, version));
|
|
594
1090
|
}
|
|
595
1091
|
return apiInstanceMap.get(`${baseUrl}--${version}`);
|
|
596
1092
|
}
|
|
1093
|
+
function transformBranchInfo(raw) {
|
|
1094
|
+
return {
|
|
1095
|
+
cursor: raw.cursor,
|
|
1096
|
+
name: raw.name,
|
|
1097
|
+
headSha: raw.head_sha,
|
|
1098
|
+
createdAt: raw.created_at
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
function transformListBranchesResult(raw) {
|
|
1102
|
+
return {
|
|
1103
|
+
branches: raw.branches.map(transformBranchInfo),
|
|
1104
|
+
nextCursor: raw.next_cursor ?? void 0,
|
|
1105
|
+
hasMore: raw.has_more
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
function transformCommitInfo(raw) {
|
|
1109
|
+
const parsedDate = new Date(raw.date);
|
|
1110
|
+
return {
|
|
1111
|
+
sha: raw.sha,
|
|
1112
|
+
message: raw.message,
|
|
1113
|
+
authorName: raw.author_name,
|
|
1114
|
+
authorEmail: raw.author_email,
|
|
1115
|
+
committerName: raw.committer_name,
|
|
1116
|
+
committerEmail: raw.committer_email,
|
|
1117
|
+
date: parsedDate,
|
|
1118
|
+
rawDate: raw.date
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
function transformListCommitsResult(raw) {
|
|
1122
|
+
return {
|
|
1123
|
+
commits: raw.commits.map(transformCommitInfo),
|
|
1124
|
+
nextCursor: raw.next_cursor ?? void 0,
|
|
1125
|
+
hasMore: raw.has_more
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
function normalizeDiffState(rawState) {
|
|
1129
|
+
if (!rawState) {
|
|
1130
|
+
return "unknown";
|
|
1131
|
+
}
|
|
1132
|
+
const leading = rawState.trim()[0]?.toUpperCase();
|
|
1133
|
+
switch (leading) {
|
|
1134
|
+
case "A":
|
|
1135
|
+
return "added";
|
|
1136
|
+
case "M":
|
|
1137
|
+
return "modified";
|
|
1138
|
+
case "D":
|
|
1139
|
+
return "deleted";
|
|
1140
|
+
case "R":
|
|
1141
|
+
return "renamed";
|
|
1142
|
+
case "C":
|
|
1143
|
+
return "copied";
|
|
1144
|
+
case "T":
|
|
1145
|
+
return "type_changed";
|
|
1146
|
+
case "U":
|
|
1147
|
+
return "unmerged";
|
|
1148
|
+
default:
|
|
1149
|
+
return "unknown";
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
function transformFileDiff(raw) {
|
|
1153
|
+
const normalizedState = normalizeDiffState(raw.state);
|
|
1154
|
+
return {
|
|
1155
|
+
path: raw.path,
|
|
1156
|
+
state: normalizedState,
|
|
1157
|
+
rawState: raw.state,
|
|
1158
|
+
oldPath: raw.old_path ?? void 0,
|
|
1159
|
+
raw: raw.raw,
|
|
1160
|
+
bytes: raw.bytes,
|
|
1161
|
+
isEof: raw.is_eof
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
function transformFilteredFile(raw) {
|
|
1165
|
+
const normalizedState = normalizeDiffState(raw.state);
|
|
1166
|
+
return {
|
|
1167
|
+
path: raw.path,
|
|
1168
|
+
state: normalizedState,
|
|
1169
|
+
rawState: raw.state,
|
|
1170
|
+
oldPath: raw.old_path ?? void 0,
|
|
1171
|
+
bytes: raw.bytes,
|
|
1172
|
+
isEof: raw.is_eof
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
function transformBranchDiffResult(raw) {
|
|
1176
|
+
return {
|
|
1177
|
+
branch: raw.branch,
|
|
1178
|
+
base: raw.base,
|
|
1179
|
+
stats: raw.stats,
|
|
1180
|
+
files: raw.files.map(transformFileDiff),
|
|
1181
|
+
filteredFiles: raw.filtered_files.map(transformFilteredFile)
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
function transformCommitDiffResult(raw) {
|
|
1185
|
+
return {
|
|
1186
|
+
sha: raw.sha,
|
|
1187
|
+
stats: raw.stats,
|
|
1188
|
+
files: raw.files.map(transformFileDiff),
|
|
1189
|
+
filteredFiles: raw.filtered_files.map(transformFilteredFile)
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
597
1192
|
var RepoImpl = class {
|
|
598
1193
|
constructor(id, options, generateJWT) {
|
|
599
1194
|
this.id = id;
|
|
@@ -613,10 +1208,10 @@ var RepoImpl = class {
|
|
|
613
1208
|
return url.toString();
|
|
614
1209
|
}
|
|
615
1210
|
async getFileStream(options) {
|
|
1211
|
+
const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
|
|
616
1212
|
const jwt = await this.generateJWT(this.id, {
|
|
617
1213
|
permissions: ["git:read"],
|
|
618
|
-
ttl
|
|
619
|
-
// 1hr in seconds
|
|
1214
|
+
ttl
|
|
620
1215
|
});
|
|
621
1216
|
const params = {
|
|
622
1217
|
path: options.path
|
|
@@ -627,39 +1222,46 @@ var RepoImpl = class {
|
|
|
627
1222
|
return this.api.get({ path: "repos/file", params }, jwt);
|
|
628
1223
|
}
|
|
629
1224
|
async listFiles(options) {
|
|
1225
|
+
const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
|
|
630
1226
|
const jwt = await this.generateJWT(this.id, {
|
|
631
1227
|
permissions: ["git:read"],
|
|
632
|
-
ttl
|
|
633
|
-
// 1hr in seconds
|
|
1228
|
+
ttl
|
|
634
1229
|
});
|
|
635
1230
|
const params = options?.ref ? { ref: options.ref } : void 0;
|
|
636
1231
|
const response = await this.api.get({ path: "repos/files", params }, jwt);
|
|
637
|
-
|
|
1232
|
+
const raw = listFilesResponseSchema.parse(await response.json());
|
|
1233
|
+
return { paths: raw.paths, ref: raw.ref };
|
|
638
1234
|
}
|
|
639
1235
|
async listBranches(options) {
|
|
1236
|
+
const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
|
|
640
1237
|
const jwt = await this.generateJWT(this.id, {
|
|
641
1238
|
permissions: ["git:read"],
|
|
642
|
-
ttl
|
|
643
|
-
// 1hr in seconds
|
|
1239
|
+
ttl
|
|
644
1240
|
});
|
|
1241
|
+
const cursor = options?.cursor;
|
|
1242
|
+
const limit = options?.limit;
|
|
645
1243
|
let params;
|
|
646
|
-
if (
|
|
1244
|
+
if (typeof cursor === "string" || typeof limit === "number") {
|
|
647
1245
|
params = {};
|
|
648
|
-
if (
|
|
649
|
-
params.cursor =
|
|
1246
|
+
if (typeof cursor === "string") {
|
|
1247
|
+
params.cursor = cursor;
|
|
650
1248
|
}
|
|
651
|
-
if (typeof
|
|
652
|
-
params.limit =
|
|
1249
|
+
if (typeof limit === "number") {
|
|
1250
|
+
params.limit = limit.toString();
|
|
653
1251
|
}
|
|
654
1252
|
}
|
|
655
1253
|
const response = await this.api.get({ path: "repos/branches", params }, jwt);
|
|
656
|
-
|
|
1254
|
+
const raw = listBranchesResponseSchema.parse(await response.json());
|
|
1255
|
+
return transformListBranchesResult({
|
|
1256
|
+
...raw,
|
|
1257
|
+
next_cursor: raw.next_cursor ?? void 0
|
|
1258
|
+
});
|
|
657
1259
|
}
|
|
658
1260
|
async listCommits(options) {
|
|
1261
|
+
const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
|
|
659
1262
|
const jwt = await this.generateJWT(this.id, {
|
|
660
1263
|
permissions: ["git:read"],
|
|
661
|
-
ttl
|
|
662
|
-
// 1hr in seconds
|
|
1264
|
+
ttl
|
|
663
1265
|
});
|
|
664
1266
|
let params;
|
|
665
1267
|
if (options?.branch || options?.cursor || options?.limit) {
|
|
@@ -675,13 +1277,17 @@ var RepoImpl = class {
|
|
|
675
1277
|
}
|
|
676
1278
|
}
|
|
677
1279
|
const response = await this.api.get({ path: "repos/commits", params }, jwt);
|
|
678
|
-
|
|
1280
|
+
const raw = listCommitsResponseSchema.parse(await response.json());
|
|
1281
|
+
return transformListCommitsResult({
|
|
1282
|
+
...raw,
|
|
1283
|
+
next_cursor: raw.next_cursor ?? void 0
|
|
1284
|
+
});
|
|
679
1285
|
}
|
|
680
1286
|
async getBranchDiff(options) {
|
|
1287
|
+
const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
|
|
681
1288
|
const jwt = await this.generateJWT(this.id, {
|
|
682
1289
|
permissions: ["git:read"],
|
|
683
|
-
ttl
|
|
684
|
-
// 1hr in seconds
|
|
1290
|
+
ttl
|
|
685
1291
|
});
|
|
686
1292
|
const params = {
|
|
687
1293
|
branch: options.branch
|
|
@@ -690,38 +1296,27 @@ var RepoImpl = class {
|
|
|
690
1296
|
params.base = options.base;
|
|
691
1297
|
}
|
|
692
1298
|
const response = await this.api.get({ path: "repos/branches/diff", params }, jwt);
|
|
693
|
-
|
|
1299
|
+
const raw = branchDiffResponseSchema.parse(await response.json());
|
|
1300
|
+
return transformBranchDiffResult(raw);
|
|
694
1301
|
}
|
|
695
1302
|
async getCommitDiff(options) {
|
|
1303
|
+
const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
|
|
696
1304
|
const jwt = await this.generateJWT(this.id, {
|
|
697
1305
|
permissions: ["git:read"],
|
|
698
|
-
ttl
|
|
699
|
-
// 1hr in seconds
|
|
1306
|
+
ttl
|
|
700
1307
|
});
|
|
701
1308
|
const params = {
|
|
702
1309
|
sha: options.sha
|
|
703
1310
|
};
|
|
704
1311
|
const response = await this.api.get({ path: "repos/diff", params }, jwt);
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
async getCommit(options) {
|
|
708
|
-
const jwt = await this.generateJWT(this.id, {
|
|
709
|
-
permissions: ["git:read"],
|
|
710
|
-
ttl: options?.ttl ?? 1 * 60 * 60
|
|
711
|
-
// 1hr in seconds
|
|
712
|
-
});
|
|
713
|
-
const params = {
|
|
714
|
-
repo: this.id,
|
|
715
|
-
sha: options.sha
|
|
716
|
-
};
|
|
717
|
-
const response = await this.api.get({ path: "commit", params }, jwt);
|
|
718
|
-
return await response.json();
|
|
1312
|
+
const raw = commitDiffResponseSchema.parse(await response.json());
|
|
1313
|
+
return transformCommitDiffResult(raw);
|
|
719
1314
|
}
|
|
720
1315
|
async pullUpstream(options) {
|
|
1316
|
+
const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
|
|
721
1317
|
const jwt = await this.generateJWT(this.id, {
|
|
722
1318
|
permissions: ["git:write"],
|
|
723
|
-
ttl
|
|
724
|
-
// 1hr in seconds
|
|
1319
|
+
ttl
|
|
725
1320
|
});
|
|
726
1321
|
const body = {};
|
|
727
1322
|
if (options.ref) {
|
|
@@ -733,15 +1328,83 @@ var RepoImpl = class {
|
|
|
733
1328
|
}
|
|
734
1329
|
return;
|
|
735
1330
|
}
|
|
1331
|
+
async restoreCommit(options) {
|
|
1332
|
+
const targetBranch = options?.targetBranch?.trim();
|
|
1333
|
+
if (!targetBranch) {
|
|
1334
|
+
throw new Error("restoreCommit targetBranch is required");
|
|
1335
|
+
}
|
|
1336
|
+
if (targetBranch.startsWith("refs/")) {
|
|
1337
|
+
throw new Error("restoreCommit targetBranch must not include refs/ prefix");
|
|
1338
|
+
}
|
|
1339
|
+
const targetCommitSha = options?.targetCommitSha?.trim();
|
|
1340
|
+
if (!targetCommitSha) {
|
|
1341
|
+
throw new Error("restoreCommit targetCommitSha is required");
|
|
1342
|
+
}
|
|
1343
|
+
const commitMessage = options?.commitMessage?.trim();
|
|
1344
|
+
const authorName = options.author?.name?.trim();
|
|
1345
|
+
const authorEmail = options.author?.email?.trim();
|
|
1346
|
+
if (!authorName || !authorEmail) {
|
|
1347
|
+
throw new Error("restoreCommit author name and email are required");
|
|
1348
|
+
}
|
|
1349
|
+
const ttl = resolveCommitTtlSeconds(options);
|
|
1350
|
+
const jwt = await this.generateJWT(this.id, {
|
|
1351
|
+
permissions: ["git:write"],
|
|
1352
|
+
ttl
|
|
1353
|
+
});
|
|
1354
|
+
const metadata = {
|
|
1355
|
+
target_branch: targetBranch,
|
|
1356
|
+
target_commit_sha: targetCommitSha,
|
|
1357
|
+
author: {
|
|
1358
|
+
name: authorName,
|
|
1359
|
+
email: authorEmail
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
if (commitMessage) {
|
|
1363
|
+
metadata.commit_message = commitMessage;
|
|
1364
|
+
}
|
|
1365
|
+
const expectedHeadSha = options.expectedHeadSha?.trim();
|
|
1366
|
+
if (expectedHeadSha) {
|
|
1367
|
+
metadata.expected_head_sha = expectedHeadSha;
|
|
1368
|
+
}
|
|
1369
|
+
if (options.committer) {
|
|
1370
|
+
const committerName = options.committer.name?.trim();
|
|
1371
|
+
const committerEmail = options.committer.email?.trim();
|
|
1372
|
+
if (!committerName || !committerEmail) {
|
|
1373
|
+
throw new Error("restoreCommit committer name and email are required when provided");
|
|
1374
|
+
}
|
|
1375
|
+
metadata.committer = {
|
|
1376
|
+
name: committerName,
|
|
1377
|
+
email: committerEmail
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
const response = await this.api.post({ path: "repos/reset-commits", body: { metadata } }, jwt, {
|
|
1381
|
+
allowedStatus: [...RESTORE_COMMIT_ALLOWED_STATUS]
|
|
1382
|
+
});
|
|
1383
|
+
const payload = await response.json();
|
|
1384
|
+
const parsed = parseRestoreCommitPayload(payload);
|
|
1385
|
+
if (parsed && "ack" in parsed) {
|
|
1386
|
+
return buildRestoreCommitResult(parsed.ack);
|
|
1387
|
+
}
|
|
1388
|
+
const failure = parsed && "failure" in parsed ? parsed.failure : void 0;
|
|
1389
|
+
const status = failure?.status ?? httpStatusToRestoreStatus(response.status);
|
|
1390
|
+
const message = failure?.message ?? `Restore commit failed with HTTP ${response.status}` + (response.statusText ? ` ${response.statusText}` : "");
|
|
1391
|
+
throw new RefUpdateError(message, {
|
|
1392
|
+
status,
|
|
1393
|
+
refUpdate: failure?.refUpdate
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
736
1396
|
createCommit(options) {
|
|
737
1397
|
const version = this.options.apiVersion ?? API_VERSION;
|
|
738
1398
|
const baseUrl = this.options.apiBaseUrl ?? API_BASE_URL;
|
|
739
1399
|
const transport = new FetchCommitTransport({ baseUrl, version });
|
|
740
|
-
const
|
|
741
|
-
const builderOptions = {
|
|
1400
|
+
const ttl = resolveCommitTtlSeconds(options);
|
|
1401
|
+
const builderOptions = {
|
|
1402
|
+
...options,
|
|
1403
|
+
ttl
|
|
1404
|
+
};
|
|
742
1405
|
const getAuthToken = () => this.generateJWT(this.id, {
|
|
743
1406
|
permissions: ["git:write"],
|
|
744
|
-
ttl
|
|
1407
|
+
ttl
|
|
745
1408
|
});
|
|
746
1409
|
return createCommitBuilder({
|
|
747
1410
|
options: builderOptions,
|
|
@@ -769,13 +1432,15 @@ var GitStorage = class _GitStorage {
|
|
|
769
1432
|
const resolvedApiBaseUrl = options.apiBaseUrl ?? _GitStorage.overrides.apiBaseUrl ?? API_BASE_URL;
|
|
770
1433
|
const resolvedApiVersion = options.apiVersion ?? _GitStorage.overrides.apiVersion ?? API_VERSION;
|
|
771
1434
|
const resolvedStorageBaseUrl = options.storageBaseUrl ?? _GitStorage.overrides.storageBaseUrl ?? STORAGE_BASE_URL;
|
|
1435
|
+
const resolvedDefaultTtl = options.defaultTTL ?? _GitStorage.overrides.defaultTTL;
|
|
772
1436
|
this.api = getApiInstance(resolvedApiBaseUrl, resolvedApiVersion);
|
|
773
1437
|
this.options = {
|
|
774
1438
|
key: options.key,
|
|
775
1439
|
name: options.name,
|
|
776
1440
|
apiBaseUrl: resolvedApiBaseUrl,
|
|
777
1441
|
apiVersion: resolvedApiVersion,
|
|
778
|
-
storageBaseUrl: resolvedStorageBaseUrl
|
|
1442
|
+
storageBaseUrl: resolvedStorageBaseUrl,
|
|
1443
|
+
defaultTTL: resolvedDefaultTtl
|
|
779
1444
|
};
|
|
780
1445
|
}
|
|
781
1446
|
static override(options) {
|
|
@@ -787,10 +1452,10 @@ var GitStorage = class _GitStorage {
|
|
|
787
1452
|
*/
|
|
788
1453
|
async createRepo(options) {
|
|
789
1454
|
const repoId = options?.id || crypto.randomUUID();
|
|
1455
|
+
const ttl = resolveInvocationTtlSeconds(options, DEFAULT_TOKEN_TTL_SECONDS);
|
|
790
1456
|
const jwt = await this.generateJWT(repoId, {
|
|
791
1457
|
permissions: ["repo:write"],
|
|
792
|
-
ttl
|
|
793
|
-
// 1hr in seconds
|
|
1458
|
+
ttl
|
|
794
1459
|
});
|
|
795
1460
|
const baseRepoOptions = options?.baseRepo ? {
|
|
796
1461
|
provider: "github",
|
|
@@ -818,7 +1483,7 @@ var GitStorage = class _GitStorage {
|
|
|
818
1483
|
async findOne(options) {
|
|
819
1484
|
const jwt = await this.generateJWT(options.id, {
|
|
820
1485
|
permissions: ["git:read"],
|
|
821
|
-
ttl:
|
|
1486
|
+
ttl: DEFAULT_TOKEN_TTL_SECONDS
|
|
822
1487
|
});
|
|
823
1488
|
const resp = await this.api.get("repo", jwt, { allowedStatus: [404] });
|
|
824
1489
|
if (resp.status === 404) {
|
|
@@ -839,7 +1504,7 @@ var GitStorage = class _GitStorage {
|
|
|
839
1504
|
*/
|
|
840
1505
|
async generateJWT(repoId, options) {
|
|
841
1506
|
const permissions = options?.permissions || ["git:write", "git:read"];
|
|
842
|
-
const ttl = options
|
|
1507
|
+
const ttl = resolveInvocationTtlSeconds(options, this.options.defaultTTL ?? 365 * 24 * 60 * 60);
|
|
843
1508
|
const now = Math.floor(Date.now() / 1e3);
|
|
844
1509
|
const payload = {
|
|
845
1510
|
iss: this.options.name,
|
|
@@ -858,7 +1523,9 @@ function createClient(options) {
|
|
|
858
1523
|
return new GitStorage(options);
|
|
859
1524
|
}
|
|
860
1525
|
|
|
1526
|
+
exports.ApiError = ApiError;
|
|
861
1527
|
exports.GitStorage = GitStorage;
|
|
1528
|
+
exports.RefUpdateError = RefUpdateError;
|
|
862
1529
|
exports.createClient = createClient;
|
|
863
1530
|
exports.parseSignatureHeader = parseSignatureHeader;
|
|
864
1531
|
exports.validateWebhook = validateWebhook;
|