@pierre/storage 0.1.1 → 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
@@ -2,6 +2,14 @@
2
2
 
3
3
  Pierre Git Storage SDK for TypeScript/JavaScript applications.
4
4
 
5
+ ## End-to-End Smoke Test
6
+
7
+ - `node packages/git-storage-sdk/tests/full-workflow.js -e production -s pierre -k /home/ian/pierre-prod-key.pem`
8
+ Drives
9
+ the Pierre workflow via the SDK: creates a repository, writes commits, fetches branch and diff
10
+ data, and confirms storage APIs. Swap in your own private key path when running outside this
11
+ workstation and adjust `-e`/`-s` for non-production environments.
12
+
5
13
  ## Installation
6
14
 
7
15
  ```bash
@@ -125,7 +133,7 @@ console.log(commitDiff.files);
125
133
  const fs = await import('node:fs/promises');
126
134
  const result = await repo
127
135
  .createCommit({
128
- targetRef: 'refs/heads/main',
136
+ targetBranch: 'main',
129
137
  commitMessage: 'Update docs',
130
138
  author: { name: 'Docs Bot', email: 'docs@example.com' },
131
139
  })
@@ -154,37 +162,44 @@ The builder exposes:
154
162
  type CommitResult = {
155
163
  commitSha: string;
156
164
  treeSha: string;
157
- targetRef: string;
165
+ targetBranch: string;
158
166
  packBytes: number;
159
167
  blobCount: number;
160
168
  refUpdate: {
161
- ref: string;
169
+ branch: string;
162
170
  oldSha: string; // All zeroes when the ref is created
163
171
  newSha: string;
164
172
  };
165
173
  };
166
174
  ```
167
175
 
168
- If the backend reports a failure (for example, the branch advanced past `baseRef`) the builder
169
- 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.
170
178
 
171
179
  **Options**
172
180
 
173
- - `targetRef` (required): Fully qualified ref that will receive the commit (for example
174
- `refs/heads/main`).
175
- - `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
176
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.
177
188
  - `commitMessage` (required): The commit message.
178
189
  - `author` (required): Include `name` and `email` for the commit author.
179
190
  - `committer` (optional): Include `name` and `email`. If omitted, the author identity is reused.
180
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.
181
194
 
182
195
  > Files are chunked into 4 MiB segments under the hood, so you can stream large assets without
183
196
  > buffering them entirely in memory. File paths are normalized relative to the repository root.
184
197
 
185
- > The `targetRef` must already exist on the remote repository. To seed an empty repository, point to
186
- > the default branch and omit `baseRef`; the service will create the first commit only when no refs
187
- > 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.
188
203
 
189
204
  ### Streaming Large Files
190
205
 
@@ -196,8 +211,8 @@ import { createReadStream } from 'node:fs';
196
211
 
197
212
  await repo
198
213
  .createCommit({
199
- targetRef: 'refs/heads/assets',
200
- baseRef: 'refs/heads/main',
214
+ targetBranch: 'assets',
215
+ expectedHeadSha: 'abc123def4567890abc123def4567890abc12345',
201
216
  commitMessage: 'Upload latest design bundle',
202
217
  author: { name: 'Assets Uploader', email: 'assets@example.com' },
203
218
  })
@@ -417,7 +432,7 @@ interface FilteredFile {
417
432
  }
418
433
 
419
434
  interface RefUpdate {
420
- ref: string;
435
+ branch: string;
421
436
  oldSha: string;
422
437
  newSha: string;
423
438
  }
@@ -425,7 +440,7 @@ interface RefUpdate {
425
440
  interface CommitResult {
426
441
  commitSha: string;
427
442
  treeSha: string;
428
- targetRef: string;
443
+ targetBranch: string;
429
444
  packBytes: number;
430
445
  blobCount: number;
431
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
  }
@@ -1377,9 +1440,13 @@ var RepoImpl = class {
1377
1440
  email: committerEmail
1378
1441
  };
1379
1442
  }
1380
- const response = await this.api.post({ path: "repos/reset-commits", body: { metadata } }, jwt, {
1381
- allowedStatus: [...RESTORE_COMMIT_ALLOWED_STATUS]
1382
- });
1443
+ const response = await this.api.post(
1444
+ { path: "repos/restore-commit", body: { metadata } },
1445
+ jwt,
1446
+ {
1447
+ allowedStatus: [...RESTORE_COMMIT_ALLOWED_STATUS]
1448
+ }
1449
+ );
1383
1450
  const payload = await response.json();
1384
1451
  const parsed = parseRestoreCommitPayload(payload);
1385
1452
  if (parsed && "ack" in parsed) {