gitd-client 0.0.0-stage → 0.7.0
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 +46 -2
- package/dist/calls.d.ts +19 -0
- package/dist/calls.js +32 -0
- package/dist/convert.d.ts +12 -0
- package/dist/convert.js +80 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +30 -0
- package/dist/gen/gitd/v1/gitd_pb.d.ts +1258 -0
- package/dist/gen/gitd/v1/gitd_pb.js +336 -0
- package/dist/gitd.d.ts +36 -0
- package/dist/gitd.js +115 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/types.d.ts +126 -0
- package/dist/types.js +1 -0
- package/package.json +50 -4
package/README.md
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
-
#
|
|
1
|
+
# gitd-client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript client for [gitd](https://github.com/dooservice/gitd): instant, secretless git
|
|
4
|
+
checkouts for agent sandboxes.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npm install gitd-client
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { Gitd, GitdError } from "gitd-client";
|
|
12
|
+
|
|
13
|
+
const gitd = new Gitd(); // $GITD_SOCKET, then /run/gitd/gitd.sock
|
|
14
|
+
|
|
15
|
+
const workspace = await gitd.prepare({
|
|
16
|
+
url: "https://github.com/your-org/your-repo",
|
|
17
|
+
path: "/var/lib/gitd/workspaces/agent-1",
|
|
18
|
+
});
|
|
19
|
+
console.log(workspace.commit, workspace.strategy); // "overlay": made in milliseconds
|
|
20
|
+
|
|
21
|
+
// … the agent works in workspace.path with plain git, and commits …
|
|
22
|
+
|
|
23
|
+
await gitd.publish(workspace.path, "agents/fix-login"); // pushed with the token gitd keeps
|
|
24
|
+
await gitd.remove(workspace.path);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
No call takes a credential: gitd reads and publishes with the ones it keeps.
|
|
28
|
+
|
|
29
|
+
| Method | What it does |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `prepare({ url, path, branch, commit, submodules, fullHistory, strategy, onProgress })` | A workspace of a repository at a path under gitd's roots |
|
|
32
|
+
| `update(path, { branch, submodules, onProgress })` | To the tip of its branch, or of the branch given |
|
|
33
|
+
| `checkout(path, commit, { submodules, onProgress })` | To a commit, as a rollback does |
|
|
34
|
+
| `describe(path)` | Its repository, commit, branch, uncommitted changes and submodules |
|
|
35
|
+
| `publish(path, branch)` | Its commits pushed to a new branch |
|
|
36
|
+
| `remove(path)` | Deleted; its pool stays |
|
|
37
|
+
| `probe(url)` | Whether it can be read, its default branch and branches |
|
|
38
|
+
| `info()` | Which gitd answers, and how it makes workspaces here and why |
|
|
39
|
+
| `audit({ since, url, limit })` | Every publish, done or refused, newest first |
|
|
40
|
+
|
|
41
|
+
A refusal is a `GitdError` with its `reason` (`AUTH_REQUIRED`, `NOT_FOUND`, `PATH_REFUSED`,
|
|
42
|
+
`BRANCH_REFUSED`, `BUSY`, …) and what git said. The whole contract is under `proto`.
|
|
43
|
+
|
|
44
|
+
[`examples/agent.ts`](examples/agent.ts) is an agent's round trip: prepare, commit, publish.
|
|
45
|
+
|
|
46
|
+
Node 22 or later. Generated from gitd's contract with `npm run generate`; built with
|
|
47
|
+
`npm run build`.
|
package/dist/calls.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Progress, WorkspaceResult } from "./gen/gitd/v1/gitd_pb.js";
|
|
2
|
+
/** A message of a workspace stream: progress, or the result that ends it. */
|
|
3
|
+
type Event = {
|
|
4
|
+
event: {
|
|
5
|
+
case: "progress";
|
|
6
|
+
value: Progress;
|
|
7
|
+
} | {
|
|
8
|
+
case: "result";
|
|
9
|
+
value: WorkspaceResult;
|
|
10
|
+
} | {
|
|
11
|
+
case: undefined;
|
|
12
|
+
value?: undefined;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
/** Reads a stream to its result, handing the progress on. */
|
|
16
|
+
export declare function drain(stream: AsyncIterable<Event>, onProgress?: (progress: Progress) => void): Promise<WorkspaceResult>;
|
|
17
|
+
/** Makes a unary call, reading a refusal into a GitdError. */
|
|
18
|
+
export declare function call<T>(request: () => Promise<T>): Promise<T>;
|
|
19
|
+
export {};
|
package/dist/calls.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Code } from "@connectrpc/connect";
|
|
2
|
+
import { GitdError, refusal } from "./errors.js";
|
|
3
|
+
/** Reads a stream to its result, handing the progress on. */
|
|
4
|
+
export async function drain(stream, onProgress) {
|
|
5
|
+
try {
|
|
6
|
+
let result;
|
|
7
|
+
for await (const message of stream) {
|
|
8
|
+
if (message.event.case === "progress") {
|
|
9
|
+
onProgress?.(message.event.value);
|
|
10
|
+
}
|
|
11
|
+
else if (message.event.case === "result") {
|
|
12
|
+
result = message.event.value;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if (!result) {
|
|
16
|
+
throw new GitdError("", "gitd ended the call without a result", Code.Internal);
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
throw refusal(error);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Makes a unary call, reading a refusal into a GitdError. */
|
|
25
|
+
export async function call(request) {
|
|
26
|
+
try {
|
|
27
|
+
return await request();
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw refusal(error);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MessageInitShape } from "@bufbuild/protobuf";
|
|
2
|
+
import { ProbeResponse_Access, Strategy, type AuditEntry as AuditEntryMessage, type PublishedRepository as PublishedRepositoryMessage, type RevisionSchema, type WorkspaceResult } from "./gen/gitd/v1/gitd_pb.js";
|
|
3
|
+
import type { AuditEntry, Probe, PublishedRepository, StrategyName, Workspace } from "./types.js";
|
|
4
|
+
export declare function toWorkspace(path: string, result: WorkspaceResult): Workspace;
|
|
5
|
+
export declare function strategyName(strategy: Strategy): StrategyName;
|
|
6
|
+
export declare function strategyProto(name?: StrategyName): Strategy;
|
|
7
|
+
export declare function accessName(access: ProbeResponse_Access): Probe["access"];
|
|
8
|
+
/** The revision a prepare asks for: a commit, a branch, or the default branch. */
|
|
9
|
+
export declare function revisionOf(branch?: string, commit?: string): MessageInitShape<typeof RevisionSchema> | undefined;
|
|
10
|
+
/** What a publish pushed: the workspace's repository first, then each submodule. */
|
|
11
|
+
export declare function pushedOf(repository: PublishedRepositoryMessage | undefined, submodules: PublishedRepositoryMessage[]): PublishedRepository[];
|
|
12
|
+
export declare function toAuditEntry(entry: AuditEntryMessage): AuditEntry;
|
package/dist/convert.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
2
|
+
import { GitFailure_Reason, ProbeResponse_Access, Strategy, } from "./gen/gitd/v1/gitd_pb.js";
|
|
3
|
+
export function toWorkspace(path, result) {
|
|
4
|
+
const layers = result.layers;
|
|
5
|
+
return {
|
|
6
|
+
path,
|
|
7
|
+
commit: result.commit,
|
|
8
|
+
branch: result.branch,
|
|
9
|
+
strategy: strategyName(result.strategy),
|
|
10
|
+
layers: layers && { lower: layers.lower, upper: layers.upper, work: layers.work },
|
|
11
|
+
pendingSubmodules: result.pendingSubmodules.map((submodule) => ({
|
|
12
|
+
path: submodule.path,
|
|
13
|
+
url: submodule.url,
|
|
14
|
+
reason: submodule.failure?.gitSaid ?? "",
|
|
15
|
+
})),
|
|
16
|
+
lfsPatterns: result.lfsPatterns,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function strategyName(strategy) {
|
|
20
|
+
switch (strategy) {
|
|
21
|
+
case Strategy.OVERLAY:
|
|
22
|
+
return "overlay";
|
|
23
|
+
case Strategy.REFLINK:
|
|
24
|
+
return "reflink";
|
|
25
|
+
default:
|
|
26
|
+
return "copy";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function strategyProto(name) {
|
|
30
|
+
switch (name) {
|
|
31
|
+
case "overlay":
|
|
32
|
+
return Strategy.OVERLAY;
|
|
33
|
+
case "reflink":
|
|
34
|
+
return Strategy.REFLINK;
|
|
35
|
+
case "copy":
|
|
36
|
+
return Strategy.COPY;
|
|
37
|
+
default:
|
|
38
|
+
return Strategy.UNSPECIFIED;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function accessName(access) {
|
|
42
|
+
switch (access) {
|
|
43
|
+
case ProbeResponse_Access.PUBLIC:
|
|
44
|
+
return "public";
|
|
45
|
+
case ProbeResponse_Access.REACHABLE:
|
|
46
|
+
return "reachable";
|
|
47
|
+
default:
|
|
48
|
+
return "denied";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** The revision a prepare asks for: a commit, a branch, or the default branch. */
|
|
52
|
+
export function revisionOf(branch, commit) {
|
|
53
|
+
if (commit) {
|
|
54
|
+
return { target: { case: "commit", value: commit } };
|
|
55
|
+
}
|
|
56
|
+
if (branch) {
|
|
57
|
+
return { target: { case: "branch", value: branch } };
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
/** What a publish pushed: the workspace's repository first, then each submodule. */
|
|
62
|
+
export function pushedOf(repository, submodules) {
|
|
63
|
+
return [repository, ...submodules]
|
|
64
|
+
.filter((pushed) => pushed !== undefined)
|
|
65
|
+
.map((pushed) => ({ url: pushed.url, path: pushed.path, commit: pushed.commit, subjects: pushed.subjects }));
|
|
66
|
+
}
|
|
67
|
+
export function toAuditEntry(entry) {
|
|
68
|
+
const refusal = entry.refusal;
|
|
69
|
+
return {
|
|
70
|
+
time: entry.time ? timestampDate(entry.time) : new Date(0),
|
|
71
|
+
workspace: entry.workspace,
|
|
72
|
+
url: entry.url,
|
|
73
|
+
branch: entry.branch,
|
|
74
|
+
repositories: pushedOf(entry.repository, entry.submodules),
|
|
75
|
+
refused: refusal && {
|
|
76
|
+
reason: GitFailure_Reason[refusal.reason].replace("REASON_", ""),
|
|
77
|
+
said: refusal.gitSaid,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Code } from "@connectrpc/connect";
|
|
2
|
+
/**
|
|
3
|
+
* A refusal from gitd: its reason (AUTH_REQUIRED, NOT_FOUND, PATH_REFUSED, BUSY, …) and
|
|
4
|
+
* what git said. Tell users what happened from the reason, never by parsing the message.
|
|
5
|
+
*/
|
|
6
|
+
export declare class GitdError extends Error {
|
|
7
|
+
readonly reason: string;
|
|
8
|
+
readonly said: string;
|
|
9
|
+
readonly code: Code;
|
|
10
|
+
constructor(reason: string, said: string, code: Code);
|
|
11
|
+
}
|
|
12
|
+
/** Reads a Connect error into a GitdError; anything else is passed on as it is. */
|
|
13
|
+
export declare function refusal(error: unknown): unknown;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ConnectError } from "@connectrpc/connect";
|
|
2
|
+
import { GitFailureSchema, GitFailure_Reason } from "./gen/gitd/v1/gitd_pb.js";
|
|
3
|
+
/**
|
|
4
|
+
* A refusal from gitd: its reason (AUTH_REQUIRED, NOT_FOUND, PATH_REFUSED, BUSY, …) and
|
|
5
|
+
* what git said. Tell users what happened from the reason, never by parsing the message.
|
|
6
|
+
*/
|
|
7
|
+
export class GitdError extends Error {
|
|
8
|
+
reason;
|
|
9
|
+
said;
|
|
10
|
+
code;
|
|
11
|
+
constructor(reason, said, code) {
|
|
12
|
+
super(reason ? `${reason}: ${said}` : said);
|
|
13
|
+
this.reason = reason;
|
|
14
|
+
this.said = said;
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.name = "GitdError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Reads a Connect error into a GitdError; anything else is passed on as it is. */
|
|
20
|
+
export function refusal(error) {
|
|
21
|
+
if (error instanceof GitdError || !(error instanceof ConnectError)) {
|
|
22
|
+
return error;
|
|
23
|
+
}
|
|
24
|
+
const [failure] = error.findDetails(GitFailureSchema);
|
|
25
|
+
if (!failure) {
|
|
26
|
+
return new GitdError("", error.rawMessage, error.code);
|
|
27
|
+
}
|
|
28
|
+
const reason = GitFailure_Reason[failure.reason].replace("REASON_", "");
|
|
29
|
+
return new GitdError(reason, failure.gitSaid, error.code);
|
|
30
|
+
}
|