@pierre/storage 0.1.4 → 0.2.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 +28 -0
- package/dist/index.cjs +576 -323
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +576 -323
- package/dist/index.js.map +1 -1
- package/package.json +38 -39
- package/src/commit-pack.ts +128 -0
- package/src/commit.ts +35 -360
- package/src/diff-commit.ts +300 -0
- package/src/index.ts +39 -4
- package/src/stream-utils.ts +255 -0
- package/src/types.ts +20 -0
package/package.json
CHANGED
|
@@ -1,40 +1,39 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
2
|
+
"name": "@pierre/storage",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Pierre Git Storage SDK",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"jose": "^5.10.0",
|
|
24
|
+
"snakecase-keys": "^9.0.2",
|
|
25
|
+
"zod": "^3.23.8"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"tsup": "8.5.0",
|
|
29
|
+
"typescript": "5.8.3",
|
|
30
|
+
"vitest": "3.2.4"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsup",
|
|
37
|
+
"dev": "tsup --watch"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { inferRefUpdateReason, RefUpdateError } from './errors';
|
|
2
|
+
import type { CommitPackAckRaw } from './schemas';
|
|
3
|
+
import { commitPackResponseSchema, errorEnvelopeSchema } from './schemas';
|
|
4
|
+
import type { CommitResult, RefUpdate } from './types';
|
|
5
|
+
|
|
6
|
+
export type CommitPackAck = CommitPackAckRaw;
|
|
7
|
+
|
|
8
|
+
export function buildCommitResult(ack: CommitPackAckRaw): CommitResult {
|
|
9
|
+
const refUpdate = toRefUpdate(ack.result);
|
|
10
|
+
if (!ack.result.success) {
|
|
11
|
+
throw new RefUpdateError(
|
|
12
|
+
ack.result.message ?? `Commit failed with status ${ack.result.status}`,
|
|
13
|
+
{
|
|
14
|
+
status: ack.result.status,
|
|
15
|
+
message: ack.result.message,
|
|
16
|
+
refUpdate,
|
|
17
|
+
},
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
commitSha: ack.commit.commit_sha,
|
|
22
|
+
treeSha: ack.commit.tree_sha,
|
|
23
|
+
targetBranch: ack.commit.target_branch,
|
|
24
|
+
packBytes: ack.commit.pack_bytes,
|
|
25
|
+
blobCount: ack.commit.blob_count,
|
|
26
|
+
refUpdate,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function toRefUpdate(result: CommitPackAckRaw['result']): RefUpdate {
|
|
31
|
+
return {
|
|
32
|
+
branch: result.branch,
|
|
33
|
+
oldSha: result.old_sha,
|
|
34
|
+
newSha: result.new_sha,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function parseCommitPackError(
|
|
39
|
+
response: Response,
|
|
40
|
+
fallbackMessage: string,
|
|
41
|
+
): Promise<{
|
|
42
|
+
statusMessage: string;
|
|
43
|
+
statusLabel: string;
|
|
44
|
+
refUpdate?: Partial<RefUpdate>;
|
|
45
|
+
}> {
|
|
46
|
+
const cloned = response.clone();
|
|
47
|
+
let jsonBody: unknown;
|
|
48
|
+
try {
|
|
49
|
+
jsonBody = await cloned.json();
|
|
50
|
+
} catch {
|
|
51
|
+
jsonBody = undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let textBody: string | undefined;
|
|
55
|
+
if (jsonBody === undefined) {
|
|
56
|
+
try {
|
|
57
|
+
textBody = await response.text();
|
|
58
|
+
} catch {
|
|
59
|
+
textBody = undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const defaultStatus = (() => {
|
|
64
|
+
const inferred = inferRefUpdateReason(String(response.status));
|
|
65
|
+
return inferred === 'unknown' ? 'failed' : inferred;
|
|
66
|
+
})();
|
|
67
|
+
let statusLabel = defaultStatus;
|
|
68
|
+
let refUpdate: Partial<RefUpdate> | undefined;
|
|
69
|
+
let message: string | undefined;
|
|
70
|
+
|
|
71
|
+
if (jsonBody !== undefined) {
|
|
72
|
+
const parsedResponse = commitPackResponseSchema.safeParse(jsonBody);
|
|
73
|
+
if (parsedResponse.success) {
|
|
74
|
+
const result = parsedResponse.data.result;
|
|
75
|
+
if (typeof result.status === 'string' && result.status.trim() !== '') {
|
|
76
|
+
statusLabel = result.status.trim() as typeof statusLabel;
|
|
77
|
+
}
|
|
78
|
+
refUpdate = toPartialRefUpdateFields(result.branch, result.old_sha, result.new_sha);
|
|
79
|
+
if (typeof result.message === 'string' && result.message.trim() !== '') {
|
|
80
|
+
message = result.message.trim();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!message) {
|
|
85
|
+
const parsedError = errorEnvelopeSchema.safeParse(jsonBody);
|
|
86
|
+
if (parsedError.success) {
|
|
87
|
+
const trimmed = parsedError.data.error.trim();
|
|
88
|
+
if (trimmed) {
|
|
89
|
+
message = trimmed;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!message && typeof jsonBody === 'string' && jsonBody.trim() !== '') {
|
|
96
|
+
message = jsonBody.trim();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!message && textBody && textBody.trim() !== '') {
|
|
100
|
+
message = textBody.trim();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
statusMessage: message ?? fallbackMessage,
|
|
105
|
+
statusLabel,
|
|
106
|
+
refUpdate,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function toPartialRefUpdateFields(
|
|
111
|
+
branch?: string | null,
|
|
112
|
+
oldSha?: string | null,
|
|
113
|
+
newSha?: string | null,
|
|
114
|
+
): Partial<RefUpdate> | undefined {
|
|
115
|
+
const refUpdate: Partial<RefUpdate> = {};
|
|
116
|
+
|
|
117
|
+
if (typeof branch === 'string' && branch.trim() !== '') {
|
|
118
|
+
refUpdate.branch = branch.trim();
|
|
119
|
+
}
|
|
120
|
+
if (typeof oldSha === 'string' && oldSha.trim() !== '') {
|
|
121
|
+
refUpdate.oldSha = oldSha.trim();
|
|
122
|
+
}
|
|
123
|
+
if (typeof newSha === 'string' && newSha.trim() !== '') {
|
|
124
|
+
refUpdate.newSha = newSha.trim();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return Object.keys(refUpdate).length > 0 ? refUpdate : undefined;
|
|
128
|
+
}
|
package/src/commit.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildCommitResult, parseCommitPackError } from './commit-pack';
|
|
2
|
+
import { RefUpdateError } from './errors';
|
|
2
3
|
import type { CommitPackAckRaw } from './schemas';
|
|
3
|
-
import { commitPackAckSchema
|
|
4
|
+
import { commitPackAckSchema } from './schemas';
|
|
5
|
+
import {
|
|
6
|
+
base64Encode,
|
|
7
|
+
type ChunkSegment,
|
|
8
|
+
chunkify,
|
|
9
|
+
requiresDuplex,
|
|
10
|
+
toAsyncIterable,
|
|
11
|
+
toRequestBody,
|
|
12
|
+
} from './stream-utils';
|
|
4
13
|
import type {
|
|
5
|
-
BlobLike,
|
|
6
14
|
CommitBuilder,
|
|
7
15
|
CommitFileOptions,
|
|
8
16
|
CommitFileSource,
|
|
@@ -11,11 +19,8 @@ import type {
|
|
|
11
19
|
CommitTextFileOptions,
|
|
12
20
|
CreateCommitOptions,
|
|
13
21
|
LegacyCreateCommitOptions,
|
|
14
|
-
ReadableStreamLike,
|
|
15
|
-
RefUpdate,
|
|
16
22
|
} from './types';
|
|
17
23
|
|
|
18
|
-
const MAX_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
19
24
|
const DEFAULT_TTL_SECONDS = 60 * 60;
|
|
20
25
|
const HEADS_REF_PREFIX = 'refs/heads/';
|
|
21
26
|
|
|
@@ -30,16 +35,13 @@ const BufferCtor: NodeBufferConstructor | undefined = (
|
|
|
30
35
|
globalThis as { Buffer?: NodeBufferConstructor }
|
|
31
36
|
).Buffer;
|
|
32
37
|
|
|
33
|
-
type ChunkSegment = {
|
|
34
|
-
chunk: Uint8Array;
|
|
35
|
-
eof: boolean;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
38
|
interface CommitMetadataPayload {
|
|
39
39
|
target_branch: string;
|
|
40
40
|
expected_head_sha?: string;
|
|
41
41
|
base_branch?: string;
|
|
42
42
|
commit_message: string;
|
|
43
|
+
ephemeral?: boolean;
|
|
44
|
+
ephemeral_base?: boolean;
|
|
43
45
|
author: {
|
|
44
46
|
name: string;
|
|
45
47
|
email: string;
|
|
@@ -64,7 +66,7 @@ interface CommitTransportRequest {
|
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
interface CommitTransport {
|
|
67
|
-
send(request: CommitTransportRequest): Promise<
|
|
69
|
+
send(request: CommitTransportRequest): Promise<CommitPackAckRaw>;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
type NormalizedCommitOptions = {
|
|
@@ -72,6 +74,8 @@ type NormalizedCommitOptions = {
|
|
|
72
74
|
commitMessage: string;
|
|
73
75
|
expectedHeadSha?: string;
|
|
74
76
|
baseBranch?: string;
|
|
77
|
+
ephemeral?: boolean;
|
|
78
|
+
ephemeralBase?: boolean;
|
|
75
79
|
author: CommitSignature;
|
|
76
80
|
committer?: CommitSignature;
|
|
77
81
|
signal?: AbortSignal;
|
|
@@ -92,8 +96,6 @@ type FileOperationState = {
|
|
|
92
96
|
streamFactory?: () => AsyncIterable<Uint8Array>;
|
|
93
97
|
};
|
|
94
98
|
|
|
95
|
-
type CommitPackAck = CommitPackAckRaw;
|
|
96
|
-
|
|
97
99
|
export class CommitBuilderImpl implements CommitBuilder {
|
|
98
100
|
private readonly options: NormalizedCommitOptions;
|
|
99
101
|
private readonly getAuthToken: () => Promise<string>;
|
|
@@ -135,6 +137,10 @@ export class CommitBuilderImpl implements CommitBuilder {
|
|
|
135
137
|
this.options.baseBranch = trimmedBase;
|
|
136
138
|
}
|
|
137
139
|
}
|
|
140
|
+
|
|
141
|
+
if (this.options.ephemeralBase && !this.options.baseBranch) {
|
|
142
|
+
throw new Error('createCommit ephemeralBase requires baseBranch');
|
|
143
|
+
}
|
|
138
144
|
}
|
|
139
145
|
|
|
140
146
|
addFile(path: string, source: CommitFileSource, options?: CommitFileOptions): CommitBuilder {
|
|
@@ -246,6 +252,13 @@ export class CommitBuilderImpl implements CommitBuilder {
|
|
|
246
252
|
};
|
|
247
253
|
}
|
|
248
254
|
|
|
255
|
+
if (this.options.ephemeral) {
|
|
256
|
+
metadata.ephemeral = true;
|
|
257
|
+
}
|
|
258
|
+
if (this.options.ephemeralBase) {
|
|
259
|
+
metadata.ephemeral_base = true;
|
|
260
|
+
}
|
|
261
|
+
|
|
249
262
|
return metadata;
|
|
250
263
|
}
|
|
251
264
|
|
|
@@ -271,7 +284,7 @@ export class FetchCommitTransport implements CommitTransport {
|
|
|
271
284
|
this.url = `${trimmedBase}/api/v${config.version}/repos/commit-pack`;
|
|
272
285
|
}
|
|
273
286
|
|
|
274
|
-
async send(request: CommitTransportRequest): Promise<
|
|
287
|
+
async send(request: CommitTransportRequest): Promise<CommitPackAckRaw> {
|
|
275
288
|
const bodyIterable = buildMessageIterable(request.metadata, request.blobs);
|
|
276
289
|
const body = toRequestBody(bodyIterable);
|
|
277
290
|
|
|
@@ -293,7 +306,11 @@ export class FetchCommitTransport implements CommitTransport {
|
|
|
293
306
|
const response = await fetch(this.url, init);
|
|
294
307
|
|
|
295
308
|
if (!response.ok) {
|
|
296
|
-
const
|
|
309
|
+
const fallbackMessage = `createCommit request failed (${response.status} ${response.statusText})`;
|
|
310
|
+
const { statusMessage, statusLabel, refUpdate } = await parseCommitPackError(
|
|
311
|
+
response,
|
|
312
|
+
fallbackMessage,
|
|
313
|
+
);
|
|
297
314
|
throw new RefUpdateError(statusMessage, {
|
|
298
315
|
status: statusLabel,
|
|
299
316
|
message: statusMessage,
|
|
@@ -306,31 +323,6 @@ export class FetchCommitTransport implements CommitTransport {
|
|
|
306
323
|
}
|
|
307
324
|
}
|
|
308
325
|
|
|
309
|
-
function toRequestBody(iterable: AsyncIterable<Uint8Array>): unknown {
|
|
310
|
-
const readableStreamCtor = (
|
|
311
|
-
globalThis as { ReadableStream?: new (underlyingSource: unknown) => unknown }
|
|
312
|
-
).ReadableStream;
|
|
313
|
-
if (typeof readableStreamCtor === 'function') {
|
|
314
|
-
const iterator = iterable[Symbol.asyncIterator]();
|
|
315
|
-
return new readableStreamCtor({
|
|
316
|
-
async pull(controller: { enqueue(chunk: Uint8Array): void; close(): void }) {
|
|
317
|
-
const { value, done } = await iterator.next();
|
|
318
|
-
if (done) {
|
|
319
|
-
controller.close();
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
controller.enqueue(value!);
|
|
323
|
-
},
|
|
324
|
-
async cancel(reason: unknown) {
|
|
325
|
-
if (typeof iterator.return === 'function') {
|
|
326
|
-
await iterator.return(reason);
|
|
327
|
-
}
|
|
328
|
-
},
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
return iterable;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
326
|
function buildMessageIterable(
|
|
335
327
|
metadata: CommitMetadataPayload,
|
|
336
328
|
blobs: Array<{ contentId: string; chunks: AsyncIterable<ChunkSegment> }>,
|
|
@@ -355,235 +347,6 @@ function buildMessageIterable(
|
|
|
355
347
|
};
|
|
356
348
|
}
|
|
357
349
|
|
|
358
|
-
function requiresDuplex(body: unknown): boolean {
|
|
359
|
-
if (!body || typeof body !== 'object') {
|
|
360
|
-
return false;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
if (typeof (body as { [Symbol.asyncIterator]?: unknown })[Symbol.asyncIterator] === 'function') {
|
|
364
|
-
return true;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
const readableStreamCtor = (globalThis as {
|
|
368
|
-
ReadableStream?: new (...args: unknown[]) => unknown;
|
|
369
|
-
}).ReadableStream;
|
|
370
|
-
if (readableStreamCtor && body instanceof readableStreamCtor) {
|
|
371
|
-
return true;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return false;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function buildCommitResult(ack: CommitPackAck): CommitResult {
|
|
378
|
-
const refUpdate = toRefUpdate(ack.result);
|
|
379
|
-
if (!ack.result.success) {
|
|
380
|
-
throw new RefUpdateError(
|
|
381
|
-
ack.result.message ?? `Commit failed with status ${ack.result.status}`,
|
|
382
|
-
{
|
|
383
|
-
status: ack.result.status,
|
|
384
|
-
message: ack.result.message,
|
|
385
|
-
refUpdate,
|
|
386
|
-
},
|
|
387
|
-
);
|
|
388
|
-
}
|
|
389
|
-
return {
|
|
390
|
-
commitSha: ack.commit.commit_sha,
|
|
391
|
-
treeSha: ack.commit.tree_sha,
|
|
392
|
-
targetBranch: ack.commit.target_branch,
|
|
393
|
-
packBytes: ack.commit.pack_bytes,
|
|
394
|
-
blobCount: ack.commit.blob_count,
|
|
395
|
-
refUpdate,
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
function toRefUpdate(result: CommitPackAck['result']): RefUpdate {
|
|
400
|
-
return {
|
|
401
|
-
branch: result.branch,
|
|
402
|
-
oldSha: result.old_sha,
|
|
403
|
-
newSha: result.new_sha,
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
async function* chunkify(source: AsyncIterable<Uint8Array>): AsyncIterable<ChunkSegment> {
|
|
408
|
-
let pending: Uint8Array | null = null;
|
|
409
|
-
let produced = false;
|
|
410
|
-
|
|
411
|
-
for await (const value of source) {
|
|
412
|
-
const bytes = value;
|
|
413
|
-
|
|
414
|
-
if (pending && pending.byteLength === MAX_CHUNK_BYTES) {
|
|
415
|
-
yield { chunk: pending, eof: false };
|
|
416
|
-
produced = true;
|
|
417
|
-
pending = null;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
const merged: Uint8Array = pending ? concatChunks(pending, bytes) : bytes;
|
|
421
|
-
pending = null;
|
|
422
|
-
|
|
423
|
-
let cursor: Uint8Array = merged;
|
|
424
|
-
while (cursor.byteLength > MAX_CHUNK_BYTES) {
|
|
425
|
-
const chunk: Uint8Array = cursor.slice(0, MAX_CHUNK_BYTES);
|
|
426
|
-
cursor = cursor.slice(MAX_CHUNK_BYTES);
|
|
427
|
-
yield { chunk, eof: false };
|
|
428
|
-
produced = true;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
pending = cursor;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (pending) {
|
|
435
|
-
yield { chunk: pending, eof: true };
|
|
436
|
-
produced = true;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
if (!produced) {
|
|
440
|
-
yield { chunk: new Uint8Array(0), eof: true };
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
async function* toAsyncIterable(source: CommitFileSource): AsyncIterable<Uint8Array> {
|
|
445
|
-
if (typeof source === 'string') {
|
|
446
|
-
yield new TextEncoder().encode(source);
|
|
447
|
-
return;
|
|
448
|
-
}
|
|
449
|
-
if (source instanceof Uint8Array) {
|
|
450
|
-
yield source;
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
if (source instanceof ArrayBuffer) {
|
|
454
|
-
yield new Uint8Array(source);
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
if (ArrayBuffer.isView(source)) {
|
|
458
|
-
yield new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
if (isBlobLike(source)) {
|
|
462
|
-
const stream = source.stream();
|
|
463
|
-
if (isAsyncIterable(stream)) {
|
|
464
|
-
for await (const chunk of stream as AsyncIterable<unknown>) {
|
|
465
|
-
yield ensureUint8Array(chunk);
|
|
466
|
-
}
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
if (isReadableStreamLike(stream)) {
|
|
470
|
-
yield* readReadableStream(stream);
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
if (isReadableStreamLike(source)) {
|
|
475
|
-
yield* readReadableStream(source);
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
if (isAsyncIterable(source)) {
|
|
479
|
-
for await (const chunk of source as AsyncIterable<unknown>) {
|
|
480
|
-
yield ensureUint8Array(chunk);
|
|
481
|
-
}
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
if (isIterable(source)) {
|
|
485
|
-
for (const chunk of source as Iterable<unknown>) {
|
|
486
|
-
yield ensureUint8Array(chunk);
|
|
487
|
-
}
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
throw new Error('Unsupported file source for createCommit');
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
async function* readReadableStream(stream: ReadableStreamLike<unknown>): AsyncIterable<Uint8Array> {
|
|
494
|
-
const reader = stream.getReader();
|
|
495
|
-
try {
|
|
496
|
-
while (true) {
|
|
497
|
-
const { value, done } = await reader.read();
|
|
498
|
-
if (done) {
|
|
499
|
-
break;
|
|
500
|
-
}
|
|
501
|
-
if (value !== undefined) {
|
|
502
|
-
yield ensureUint8Array(value);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
} finally {
|
|
506
|
-
reader.releaseLock?.();
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
function ensureUint8Array(value: unknown): Uint8Array {
|
|
511
|
-
if (value instanceof Uint8Array) {
|
|
512
|
-
return value;
|
|
513
|
-
}
|
|
514
|
-
if (value instanceof ArrayBuffer) {
|
|
515
|
-
return new Uint8Array(value);
|
|
516
|
-
}
|
|
517
|
-
if (ArrayBuffer.isView(value)) {
|
|
518
|
-
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
519
|
-
}
|
|
520
|
-
if (typeof value === 'string') {
|
|
521
|
-
return new TextEncoder().encode(value);
|
|
522
|
-
}
|
|
523
|
-
if (BufferCtor && BufferCtor.isBuffer(value)) {
|
|
524
|
-
return value as Uint8Array;
|
|
525
|
-
}
|
|
526
|
-
throw new Error('Unsupported chunk type; expected binary data');
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
function isBlobLike(value: unknown): value is BlobLike {
|
|
530
|
-
return (
|
|
531
|
-
typeof value === 'object' && value !== null && typeof (value as BlobLike).stream === 'function'
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
function isReadableStreamLike<T>(value: unknown): value is ReadableStreamLike<T> {
|
|
536
|
-
return (
|
|
537
|
-
typeof value === 'object' &&
|
|
538
|
-
value !== null &&
|
|
539
|
-
typeof (value as ReadableStreamLike<T>).getReader === 'function'
|
|
540
|
-
);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
function isAsyncIterable(value: unknown): value is AsyncIterable<unknown> {
|
|
544
|
-
return (
|
|
545
|
-
typeof value === 'object' &&
|
|
546
|
-
value !== null &&
|
|
547
|
-
Symbol.asyncIterator in (value as Record<string, unknown>)
|
|
548
|
-
);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
function isIterable(value: unknown): value is Iterable<unknown> {
|
|
552
|
-
return (
|
|
553
|
-
typeof value === 'object' &&
|
|
554
|
-
value !== null &&
|
|
555
|
-
Symbol.iterator in (value as Record<string, unknown>)
|
|
556
|
-
);
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
function concatChunks(a: Uint8Array, b: Uint8Array): Uint8Array {
|
|
560
|
-
if (a.byteLength === 0) {
|
|
561
|
-
return b;
|
|
562
|
-
}
|
|
563
|
-
if (b.byteLength === 0) {
|
|
564
|
-
return a;
|
|
565
|
-
}
|
|
566
|
-
const merged = new Uint8Array(a.byteLength + b.byteLength);
|
|
567
|
-
merged.set(a, 0);
|
|
568
|
-
merged.set(b, a.byteLength);
|
|
569
|
-
return merged;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
function base64Encode(bytes: Uint8Array): string {
|
|
573
|
-
if (BufferCtor) {
|
|
574
|
-
return BufferCtor.from(bytes).toString('base64');
|
|
575
|
-
}
|
|
576
|
-
let binary = '';
|
|
577
|
-
for (let i = 0; i < bytes.byteLength; i++) {
|
|
578
|
-
binary += String.fromCharCode(bytes[i]);
|
|
579
|
-
}
|
|
580
|
-
const btoaFn = (globalThis as { btoa?: (data: string) => string }).btoa;
|
|
581
|
-
if (typeof btoaFn === 'function') {
|
|
582
|
-
return btoaFn(binary);
|
|
583
|
-
}
|
|
584
|
-
throw new Error('Base64 encoding is not supported in this environment');
|
|
585
|
-
}
|
|
586
|
-
|
|
587
350
|
function randomContentId(): string {
|
|
588
351
|
const cryptoObj = globalThis.crypto;
|
|
589
352
|
if (cryptoObj && typeof cryptoObj.randomUUID === 'function') {
|
|
@@ -599,6 +362,8 @@ function normalizeCommitOptions(options: CreateCommitOptions): NormalizedCommitO
|
|
|
599
362
|
commitMessage: options.commitMessage,
|
|
600
363
|
expectedHeadSha: options.expectedHeadSha,
|
|
601
364
|
baseBranch: options.baseBranch,
|
|
365
|
+
ephemeral: options.ephemeral === true,
|
|
366
|
+
ephemeralBase: options.ephemeralBase === true,
|
|
602
367
|
author: options.author,
|
|
603
368
|
committer: options.committer,
|
|
604
369
|
signal: options.signal,
|
|
@@ -665,93 +430,3 @@ export function resolveCommitTtlSeconds(options?: { ttl?: number }): number {
|
|
|
665
430
|
}
|
|
666
431
|
return DEFAULT_TTL_SECONDS;
|
|
667
432
|
}
|
|
668
|
-
|
|
669
|
-
async function parseCommitPackError(response: Response): Promise<{
|
|
670
|
-
statusMessage: string;
|
|
671
|
-
statusLabel: string;
|
|
672
|
-
refUpdate?: Partial<RefUpdate>;
|
|
673
|
-
}> {
|
|
674
|
-
const fallbackMessage = `createCommit request failed (${response.status} ${response.statusText})`;
|
|
675
|
-
const cloned = response.clone();
|
|
676
|
-
let jsonBody: unknown;
|
|
677
|
-
try {
|
|
678
|
-
jsonBody = await cloned.json();
|
|
679
|
-
} catch {
|
|
680
|
-
jsonBody = undefined;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
let textBody: string | undefined;
|
|
684
|
-
if (jsonBody === undefined) {
|
|
685
|
-
try {
|
|
686
|
-
textBody = await response.text();
|
|
687
|
-
} catch {
|
|
688
|
-
textBody = undefined;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
const defaultStatus = (() => {
|
|
693
|
-
const inferred = inferRefUpdateReason(String(response.status));
|
|
694
|
-
return inferred === 'unknown' ? 'failed' : inferred;
|
|
695
|
-
})();
|
|
696
|
-
let statusLabel = defaultStatus;
|
|
697
|
-
let refUpdate: Partial<RefUpdate> | undefined;
|
|
698
|
-
let message: string | undefined;
|
|
699
|
-
|
|
700
|
-
if (jsonBody !== undefined) {
|
|
701
|
-
const parsedResponse = commitPackResponseSchema.safeParse(jsonBody);
|
|
702
|
-
if (parsedResponse.success) {
|
|
703
|
-
const result = parsedResponse.data.result;
|
|
704
|
-
if (typeof result.status === 'string' && result.status.trim() !== '') {
|
|
705
|
-
statusLabel = result.status.trim() as typeof statusLabel;
|
|
706
|
-
}
|
|
707
|
-
refUpdate = toPartialRefUpdateFields(result.branch, result.old_sha, result.new_sha);
|
|
708
|
-
if (typeof result.message === 'string' && result.message.trim() !== '') {
|
|
709
|
-
message = result.message.trim();
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
if (!message) {
|
|
714
|
-
const parsedError = errorEnvelopeSchema.safeParse(jsonBody);
|
|
715
|
-
if (parsedError.success) {
|
|
716
|
-
const trimmed = parsedError.data.error.trim();
|
|
717
|
-
if (trimmed) {
|
|
718
|
-
message = trimmed;
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
if (!message && typeof jsonBody === 'string' && jsonBody.trim() !== '') {
|
|
725
|
-
message = jsonBody.trim();
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
if (!message && textBody && textBody.trim() !== '') {
|
|
729
|
-
message = textBody.trim();
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
return {
|
|
733
|
-
statusMessage: message ?? fallbackMessage,
|
|
734
|
-
statusLabel,
|
|
735
|
-
refUpdate,
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
function toPartialRefUpdateFields(
|
|
740
|
-
branch?: string | null,
|
|
741
|
-
oldSha?: string | null,
|
|
742
|
-
newSha?: string | null,
|
|
743
|
-
): Partial<RefUpdate> | undefined {
|
|
744
|
-
const refUpdate: Partial<RefUpdate> = {};
|
|
745
|
-
|
|
746
|
-
if (typeof branch === 'string' && branch.trim() !== '') {
|
|
747
|
-
refUpdate.branch = branch.trim();
|
|
748
|
-
}
|
|
749
|
-
if (typeof oldSha === 'string' && oldSha.trim() !== '') {
|
|
750
|
-
refUpdate.oldSha = oldSha.trim();
|
|
751
|
-
}
|
|
752
|
-
if (typeof newSha === 'string' && newSha.trim() !== '') {
|
|
753
|
-
refUpdate.newSha = newSha.trim();
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
return Object.keys(refUpdate).length > 0 ? refUpdate : undefined;
|
|
757
|
-
}
|