@pierre/storage 0.1.2 → 0.1.3

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 CHANGED
@@ -133,7 +133,7 @@ console.log(commitDiff.files);
133
133
  const fs = await import('node:fs/promises');
134
134
  const result = await repo
135
135
  .createCommit({
136
- targetRef: 'refs/heads/main',
136
+ targetBranch: 'main',
137
137
  commitMessage: 'Update docs',
138
138
  author: { name: 'Docs Bot', email: 'docs@example.com' },
139
139
  })
@@ -162,37 +162,44 @@ The builder exposes:
162
162
  type CommitResult = {
163
163
  commitSha: string;
164
164
  treeSha: string;
165
- targetRef: string;
165
+ targetBranch: string;
166
166
  packBytes: number;
167
167
  blobCount: number;
168
168
  refUpdate: {
169
- ref: string;
169
+ branch: string;
170
170
  oldSha: string; // All zeroes when the ref is created
171
171
  newSha: string;
172
172
  };
173
173
  };
174
174
  ```
175
175
 
176
- If the backend reports a failure (for example, the branch advanced past `baseRef`) the builder
177
- throws a `RefUpdateError` containing the status, reason, and ref details.
176
+ If the backend reports a failure (for example, the branch advanced past `expectedHeadSha`) the
177
+ builder throws a `RefUpdateError` containing the status, reason, and ref details.
178
178
 
179
179
  **Options**
180
180
 
181
- - `targetRef` (required): Fully qualified ref that will receive the commit (for example
182
- `refs/heads/main`).
183
- - `baseRef` (optional): Branch or commit that must match the remote tip; omit to fast-forward
181
+ - `targetBranch` (required): Branch name (for example `main`) that will receive the commit.
182
+ - `expectedHeadSha` (optional): Commit SHA that must match the remote tip; omit to fast-forward
184
183
  unconditionally.
184
+ - `baseBranch` (optional): Mirrors the `base_branch` metadata and names an existing branch whose tip
185
+ should seed `targetBranch` if it does not exist. Leave `expectedHeadSha` empty when creating a new
186
+ branch from `baseBranch`; when both are provided and the branch already exists, `expectedHeadSha`
187
+ continues to enforce the fast-forward guard.
185
188
  - `commitMessage` (required): The commit message.
186
189
  - `author` (required): Include `name` and `email` for the commit author.
187
190
  - `committer` (optional): Include `name` and `email`. If omitted, the author identity is reused.
188
191
  - `signal` (optional): Abort an in-flight upload with `AbortController`.
192
+ - `targetRef` (deprecated, optional): Fully qualified ref (for example `refs/heads/main`). Prefer
193
+ `targetBranch`, which now accepts plain branch names.
189
194
 
190
195
  > Files are chunked into 4 MiB segments under the hood, so you can stream large assets without
191
196
  > buffering them entirely in memory. File paths are normalized relative to the repository root.
192
197
 
193
- > The `targetRef` must already exist on the remote repository. To seed an empty repository, point to
194
- > the default branch and omit `baseRef`; the service will create the first commit only when no refs
195
- > are present.
198
+ > The `targetBranch` must already exist on the remote repository unless you provide `baseBranch` (or
199
+ > the repository has no refs). To seed an empty repository, point to the default branch and omit
200
+ > `expectedHeadSha`. To create a missing branch within an existing repository, set `baseBranch` to
201
+ > the source branch and omit `expectedHeadSha` so the service clones that tip before applying your
202
+ > changes.
196
203
 
197
204
  ### Streaming Large Files
198
205
 
@@ -204,8 +211,8 @@ import { createReadStream } from 'node:fs';
204
211
 
205
212
  await repo
206
213
  .createCommit({
207
- targetRef: 'refs/heads/assets',
208
- baseRef: 'refs/heads/main',
214
+ targetBranch: 'assets',
215
+ expectedHeadSha: 'abc123def4567890abc123def4567890abc12345',
209
216
  commitMessage: 'Upload latest design bundle',
210
217
  author: { name: 'Assets Uploader', email: 'assets@example.com' },
211
218
  })
@@ -425,7 +432,7 @@ interface FilteredFile {
425
432
  }
426
433
 
427
434
  interface RefUpdate {
428
- ref: string;
435
+ branch: string;
429
436
  oldSha: string;
430
437
  newSha: string;
431
438
  }
@@ -433,7 +440,7 @@ interface RefUpdate {
433
440
  interface CommitResult {
434
441
  commitSha: string;
435
442
  treeSha: string;
436
- targetRef: string;
443
+ targetBranch: string;
437
444
  packBytes: number;
438
445
  blobCount: number;
439
446
  refUpdate: RefUpdate;
package/dist/index.cjs CHANGED
@@ -157,6 +157,7 @@ var errorEnvelopeSchema = zod.z.object({
157
157
  // src/commit.ts
158
158
  var MAX_CHUNK_BYTES = 4 * 1024 * 1024;
159
159
  var DEFAULT_TTL_SECONDS = 60 * 60;
160
+ var HEADS_REF_PREFIX = "refs/heads/";
160
161
  var BufferCtor = globalThis.Buffer;
161
162
  var CommitBuilderImpl = class {
162
163
  options;
@@ -165,26 +166,18 @@ var CommitBuilderImpl = class {
165
166
  operations = [];
166
167
  sent = false;
167
168
  constructor(deps) {
168
- this.options = { ...deps.options };
169
+ this.options = normalizeCommitOptions(deps.options);
169
170
  this.getAuthToken = deps.getAuthToken;
170
171
  this.transport = deps.transport;
171
- const trimmedTarget = this.options.targetBranch?.trim();
172
172
  const trimmedMessage = this.options.commitMessage?.trim();
173
173
  const trimmedAuthorName = this.options.author?.name?.trim();
174
174
  const trimmedAuthorEmail = this.options.author?.email?.trim();
175
- if (!trimmedTarget) {
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");
180
- }
181
175
  if (!trimmedMessage) {
182
176
  throw new Error("createCommit commitMessage is required");
183
177
  }
184
178
  if (!trimmedAuthorName || !trimmedAuthorEmail) {
185
179
  throw new Error("createCommit author name and email are required");
186
180
  }
187
- this.options.targetBranch = trimmedTarget;
188
181
  this.options.commitMessage = trimmedMessage;
189
182
  this.options.author = {
190
183
  name: trimmedAuthorName,
@@ -193,6 +186,17 @@ var CommitBuilderImpl = class {
193
186
  if (typeof this.options.expectedHeadSha === "string") {
194
187
  this.options.expectedHeadSha = this.options.expectedHeadSha.trim();
195
188
  }
189
+ if (typeof this.options.baseBranch === "string") {
190
+ const trimmedBase = this.options.baseBranch.trim();
191
+ if (trimmedBase === "") {
192
+ delete this.options.baseBranch;
193
+ } else {
194
+ if (trimmedBase.startsWith("refs/")) {
195
+ throw new Error("createCommit baseBranch must not include refs/ prefix");
196
+ }
197
+ this.options.baseBranch = trimmedBase;
198
+ }
199
+ }
196
200
  }
197
201
  addFile(path, source, options) {
198
202
  this.ensureNotSent();
@@ -277,6 +281,9 @@ var CommitBuilderImpl = class {
277
281
  if (this.options.expectedHeadSha) {
278
282
  metadata.expected_head_sha = this.options.expectedHeadSha;
279
283
  }
284
+ if (this.options.baseBranch) {
285
+ metadata.base_branch = this.options.baseBranch;
286
+ }
280
287
  if (this.options.committer) {
281
288
  metadata.committer = {
282
289
  name: this.options.committer.name,
@@ -557,6 +564,62 @@ function randomContentId() {
557
564
  const random = Math.random().toString(36).slice(2);
558
565
  return `cid-${Date.now().toString(36)}-${random}`;
559
566
  }
567
+ function normalizeCommitOptions(options) {
568
+ return {
569
+ targetBranch: resolveTargetBranch(options),
570
+ commitMessage: options.commitMessage,
571
+ expectedHeadSha: options.expectedHeadSha,
572
+ baseBranch: options.baseBranch,
573
+ author: options.author,
574
+ committer: options.committer,
575
+ signal: options.signal,
576
+ ttl: options.ttl
577
+ };
578
+ }
579
+ function resolveTargetBranch(options) {
580
+ const branchCandidate = typeof options.targetBranch === "string" ? options.targetBranch.trim() : "";
581
+ if (branchCandidate) {
582
+ return normalizeBranchName(branchCandidate);
583
+ }
584
+ if (hasLegacyTargetRef(options)) {
585
+ return normalizeLegacyTargetRef(options.targetRef);
586
+ }
587
+ throw new Error("createCommit targetBranch is required");
588
+ }
589
+ function normalizeBranchName(value) {
590
+ const trimmed = value.trim();
591
+ if (!trimmed) {
592
+ throw new Error("createCommit targetBranch is required");
593
+ }
594
+ if (trimmed.startsWith(HEADS_REF_PREFIX)) {
595
+ const branch = trimmed.slice(HEADS_REF_PREFIX.length).trim();
596
+ if (!branch) {
597
+ throw new Error("createCommit targetBranch is required");
598
+ }
599
+ return branch;
600
+ }
601
+ if (trimmed.startsWith("refs/")) {
602
+ throw new Error("createCommit targetBranch must not include refs/ prefix");
603
+ }
604
+ return trimmed;
605
+ }
606
+ function normalizeLegacyTargetRef(ref) {
607
+ const trimmed = ref.trim();
608
+ if (!trimmed) {
609
+ throw new Error("createCommit targetRef is required");
610
+ }
611
+ if (!trimmed.startsWith(HEADS_REF_PREFIX)) {
612
+ throw new Error("createCommit targetRef must start with refs/heads/");
613
+ }
614
+ const branch = trimmed.slice(HEADS_REF_PREFIX.length).trim();
615
+ if (!branch) {
616
+ throw new Error("createCommit targetRef must include a branch name");
617
+ }
618
+ return branch;
619
+ }
620
+ function hasLegacyTargetRef(options) {
621
+ return typeof options.targetRef === "string";
622
+ }
560
623
  function createCommitBuilder(deps) {
561
624
  return new CommitBuilderImpl(deps);
562
625
  }