@pierre/storage 0.1.2 → 0.1.4
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 +22 -15
- package/dist/index.cjs +91 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -3
- package/dist/index.d.ts +15 -3
- package/dist/index.js +91 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/commit.ts +120 -12
- package/src/types.ts +17 -2
package/src/commit.ts
CHANGED
|
@@ -7,14 +7,17 @@ import type {
|
|
|
7
7
|
CommitFileOptions,
|
|
8
8
|
CommitFileSource,
|
|
9
9
|
CommitResult,
|
|
10
|
+
CommitSignature,
|
|
10
11
|
CommitTextFileOptions,
|
|
11
12
|
CreateCommitOptions,
|
|
13
|
+
LegacyCreateCommitOptions,
|
|
12
14
|
ReadableStreamLike,
|
|
13
15
|
RefUpdate,
|
|
14
16
|
} from './types';
|
|
15
17
|
|
|
16
18
|
const MAX_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
17
19
|
const DEFAULT_TTL_SECONDS = 60 * 60;
|
|
20
|
+
const HEADS_REF_PREFIX = 'refs/heads/';
|
|
18
21
|
|
|
19
22
|
type NodeBuffer = Uint8Array & { toString(encoding?: string): string };
|
|
20
23
|
interface NodeBufferConstructor {
|
|
@@ -35,6 +38,7 @@ type ChunkSegment = {
|
|
|
35
38
|
interface CommitMetadataPayload {
|
|
36
39
|
target_branch: string;
|
|
37
40
|
expected_head_sha?: string;
|
|
41
|
+
base_branch?: string;
|
|
38
42
|
commit_message: string;
|
|
39
43
|
author: {
|
|
40
44
|
name: string;
|
|
@@ -63,6 +67,17 @@ interface CommitTransport {
|
|
|
63
67
|
send(request: CommitTransportRequest): Promise<CommitPackAck>;
|
|
64
68
|
}
|
|
65
69
|
|
|
70
|
+
type NormalizedCommitOptions = {
|
|
71
|
+
targetBranch: string;
|
|
72
|
+
commitMessage: string;
|
|
73
|
+
expectedHeadSha?: string;
|
|
74
|
+
baseBranch?: string;
|
|
75
|
+
author: CommitSignature;
|
|
76
|
+
committer?: CommitSignature;
|
|
77
|
+
signal?: AbortSignal;
|
|
78
|
+
ttl?: number;
|
|
79
|
+
};
|
|
80
|
+
|
|
66
81
|
interface CommitBuilderDeps {
|
|
67
82
|
options: CreateCommitOptions;
|
|
68
83
|
getAuthToken: () => Promise<string>;
|
|
@@ -80,35 +95,27 @@ type FileOperationState = {
|
|
|
80
95
|
type CommitPackAck = CommitPackAckRaw;
|
|
81
96
|
|
|
82
97
|
export class CommitBuilderImpl implements CommitBuilder {
|
|
83
|
-
private readonly options:
|
|
98
|
+
private readonly options: NormalizedCommitOptions;
|
|
84
99
|
private readonly getAuthToken: () => Promise<string>;
|
|
85
100
|
private readonly transport: CommitTransport;
|
|
86
101
|
private readonly operations: FileOperationState[] = [];
|
|
87
102
|
private sent = false;
|
|
88
103
|
|
|
89
104
|
constructor(deps: CommitBuilderDeps) {
|
|
90
|
-
this.options =
|
|
105
|
+
this.options = normalizeCommitOptions(deps.options);
|
|
91
106
|
this.getAuthToken = deps.getAuthToken;
|
|
92
107
|
this.transport = deps.transport;
|
|
93
108
|
|
|
94
|
-
const trimmedTarget = this.options.targetBranch?.trim();
|
|
95
109
|
const trimmedMessage = this.options.commitMessage?.trim();
|
|
96
110
|
const trimmedAuthorName = this.options.author?.name?.trim();
|
|
97
111
|
const trimmedAuthorEmail = this.options.author?.email?.trim();
|
|
98
112
|
|
|
99
|
-
if (!trimmedTarget) {
|
|
100
|
-
throw new Error('createCommit targetBranch is required');
|
|
101
|
-
}
|
|
102
|
-
if (trimmedTarget.startsWith('refs/')) {
|
|
103
|
-
throw new Error('createCommit targetBranch must not include refs/ prefix');
|
|
104
|
-
}
|
|
105
113
|
if (!trimmedMessage) {
|
|
106
114
|
throw new Error('createCommit commitMessage is required');
|
|
107
115
|
}
|
|
108
116
|
if (!trimmedAuthorName || !trimmedAuthorEmail) {
|
|
109
117
|
throw new Error('createCommit author name and email are required');
|
|
110
118
|
}
|
|
111
|
-
this.options.targetBranch = trimmedTarget;
|
|
112
119
|
this.options.commitMessage = trimmedMessage;
|
|
113
120
|
this.options.author = {
|
|
114
121
|
name: trimmedAuthorName,
|
|
@@ -117,6 +124,17 @@ export class CommitBuilderImpl implements CommitBuilder {
|
|
|
117
124
|
if (typeof this.options.expectedHeadSha === 'string') {
|
|
118
125
|
this.options.expectedHeadSha = this.options.expectedHeadSha.trim();
|
|
119
126
|
}
|
|
127
|
+
if (typeof this.options.baseBranch === 'string') {
|
|
128
|
+
const trimmedBase = this.options.baseBranch.trim();
|
|
129
|
+
if (trimmedBase === '') {
|
|
130
|
+
delete this.options.baseBranch;
|
|
131
|
+
} else {
|
|
132
|
+
if (trimmedBase.startsWith('refs/')) {
|
|
133
|
+
throw new Error('createCommit baseBranch must not include refs/ prefix');
|
|
134
|
+
}
|
|
135
|
+
this.options.baseBranch = trimmedBase;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
120
138
|
}
|
|
121
139
|
|
|
122
140
|
addFile(path: string, source: CommitFileSource, options?: CommitFileOptions): CommitBuilder {
|
|
@@ -218,6 +236,9 @@ export class CommitBuilderImpl implements CommitBuilder {
|
|
|
218
236
|
if (this.options.expectedHeadSha) {
|
|
219
237
|
metadata.expected_head_sha = this.options.expectedHeadSha;
|
|
220
238
|
}
|
|
239
|
+
if (this.options.baseBranch) {
|
|
240
|
+
metadata.base_branch = this.options.baseBranch;
|
|
241
|
+
}
|
|
221
242
|
if (this.options.committer) {
|
|
222
243
|
metadata.committer = {
|
|
223
244
|
name: this.options.committer.name,
|
|
@@ -254,7 +275,7 @@ export class FetchCommitTransport implements CommitTransport {
|
|
|
254
275
|
const bodyIterable = buildMessageIterable(request.metadata, request.blobs);
|
|
255
276
|
const body = toRequestBody(bodyIterable);
|
|
256
277
|
|
|
257
|
-
const
|
|
278
|
+
const init: RequestInit = {
|
|
258
279
|
method: 'POST',
|
|
259
280
|
headers: {
|
|
260
281
|
Authorization: `Bearer ${request.authorization}`,
|
|
@@ -263,7 +284,13 @@ export class FetchCommitTransport implements CommitTransport {
|
|
|
263
284
|
},
|
|
264
285
|
body: body as any,
|
|
265
286
|
signal: request.signal,
|
|
266
|
-
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
if (requiresDuplex(body)) {
|
|
290
|
+
(init as RequestInit & { duplex: 'half' }).duplex = 'half';
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const response = await fetch(this.url, init);
|
|
267
294
|
|
|
268
295
|
if (!response.ok) {
|
|
269
296
|
const { statusMessage, statusLabel, refUpdate } = await parseCommitPackError(response);
|
|
@@ -328,6 +355,25 @@ function buildMessageIterable(
|
|
|
328
355
|
};
|
|
329
356
|
}
|
|
330
357
|
|
|
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
|
+
|
|
331
377
|
function buildCommitResult(ack: CommitPackAck): CommitResult {
|
|
332
378
|
const refUpdate = toRefUpdate(ack.result);
|
|
333
379
|
if (!ack.result.success) {
|
|
@@ -547,6 +593,68 @@ function randomContentId(): string {
|
|
|
547
593
|
return `cid-${Date.now().toString(36)}-${random}`;
|
|
548
594
|
}
|
|
549
595
|
|
|
596
|
+
function normalizeCommitOptions(options: CreateCommitOptions): NormalizedCommitOptions {
|
|
597
|
+
return {
|
|
598
|
+
targetBranch: resolveTargetBranch(options),
|
|
599
|
+
commitMessage: options.commitMessage,
|
|
600
|
+
expectedHeadSha: options.expectedHeadSha,
|
|
601
|
+
baseBranch: options.baseBranch,
|
|
602
|
+
author: options.author,
|
|
603
|
+
committer: options.committer,
|
|
604
|
+
signal: options.signal,
|
|
605
|
+
ttl: options.ttl,
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function resolveTargetBranch(options: CreateCommitOptions): string {
|
|
610
|
+
const branchCandidate =
|
|
611
|
+
typeof options.targetBranch === 'string' ? options.targetBranch.trim() : '';
|
|
612
|
+
if (branchCandidate) {
|
|
613
|
+
return normalizeBranchName(branchCandidate);
|
|
614
|
+
}
|
|
615
|
+
if (hasLegacyTargetRef(options)) {
|
|
616
|
+
return normalizeLegacyTargetRef(options.targetRef);
|
|
617
|
+
}
|
|
618
|
+
throw new Error('createCommit targetBranch is required');
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function normalizeBranchName(value: string): string {
|
|
622
|
+
const trimmed = value.trim();
|
|
623
|
+
if (!trimmed) {
|
|
624
|
+
throw new Error('createCommit targetBranch is required');
|
|
625
|
+
}
|
|
626
|
+
if (trimmed.startsWith(HEADS_REF_PREFIX)) {
|
|
627
|
+
const branch = trimmed.slice(HEADS_REF_PREFIX.length).trim();
|
|
628
|
+
if (!branch) {
|
|
629
|
+
throw new Error('createCommit targetBranch is required');
|
|
630
|
+
}
|
|
631
|
+
return branch;
|
|
632
|
+
}
|
|
633
|
+
if (trimmed.startsWith('refs/')) {
|
|
634
|
+
throw new Error('createCommit targetBranch must not include refs/ prefix');
|
|
635
|
+
}
|
|
636
|
+
return trimmed;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function normalizeLegacyTargetRef(ref: string): string {
|
|
640
|
+
const trimmed = ref.trim();
|
|
641
|
+
if (!trimmed) {
|
|
642
|
+
throw new Error('createCommit targetRef is required');
|
|
643
|
+
}
|
|
644
|
+
if (!trimmed.startsWith(HEADS_REF_PREFIX)) {
|
|
645
|
+
throw new Error('createCommit targetRef must start with refs/heads/');
|
|
646
|
+
}
|
|
647
|
+
const branch = trimmed.slice(HEADS_REF_PREFIX.length).trim();
|
|
648
|
+
if (!branch) {
|
|
649
|
+
throw new Error('createCommit targetRef must include a branch name');
|
|
650
|
+
}
|
|
651
|
+
return branch;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function hasLegacyTargetRef(options: CreateCommitOptions): options is LegacyCreateCommitOptions {
|
|
655
|
+
return typeof (options as LegacyCreateCommitOptions).targetRef === 'string';
|
|
656
|
+
}
|
|
657
|
+
|
|
550
658
|
export function createCommitBuilder(deps: CommitBuilderDeps): CommitBuilder {
|
|
551
659
|
return new CommitBuilderImpl(deps);
|
|
552
660
|
}
|
package/src/types.ts
CHANGED
|
@@ -224,15 +224,30 @@ export interface FileDiff extends DiffFileBase {
|
|
|
224
224
|
|
|
225
225
|
export interface FilteredFile extends DiffFileBase {}
|
|
226
226
|
|
|
227
|
-
|
|
228
|
-
targetBranch: string;
|
|
227
|
+
interface CreateCommitBaseOptions extends GitStorageInvocationOptions {
|
|
229
228
|
commitMessage: string;
|
|
230
229
|
expectedHeadSha?: string;
|
|
230
|
+
baseBranch?: string;
|
|
231
231
|
author: CommitSignature;
|
|
232
232
|
committer?: CommitSignature;
|
|
233
233
|
signal?: AbortSignal;
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
export interface CreateCommitBranchOptions extends CreateCommitBaseOptions {
|
|
237
|
+
targetBranch: string;
|
|
238
|
+
targetRef?: never;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @deprecated Use {@link CreateCommitBranchOptions} instead.
|
|
243
|
+
*/
|
|
244
|
+
export interface LegacyCreateCommitOptions extends CreateCommitBaseOptions {
|
|
245
|
+
targetBranch?: never;
|
|
246
|
+
targetRef: string;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export type CreateCommitOptions = CreateCommitBranchOptions | LegacyCreateCommitOptions;
|
|
250
|
+
|
|
236
251
|
export interface CommitSignature {
|
|
237
252
|
name: string;
|
|
238
253
|
email: string;
|