@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 +30 -15
- package/dist/index.cjs +79 -12
- 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 +79 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/commit.ts +93 -10
- package/src/index.ts +7 -3
- package/src/types.ts +17 -2
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
|
-
|
|
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
|
-
|
|
165
|
+
targetBranch: string;
|
|
158
166
|
packBytes: number;
|
|
159
167
|
blobCount: number;
|
|
160
168
|
refUpdate: {
|
|
161
|
-
|
|
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 `
|
|
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
|
-
- `
|
|
174
|
-
|
|
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 `
|
|
186
|
-
> the
|
|
187
|
-
>
|
|
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
|
-
|
|
200
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
1381
|
-
|
|
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) {
|