gitd-client 0.0.0-stage → 0.8.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/compare-convert.d.ts +6 -0
- package/dist/compare-convert.js +33 -0
- package/dist/convert.d.ts +16 -0
- package/dist/convert.js +128 -0
- package/dist/credential-convert.d.ts +3 -0
- package/dist/credential-convert.js +27 -0
- package/dist/credential-types.d.ts +57 -0
- package/dist/credential-types.js +1 -0
- package/dist/credentials.d.ts +27 -0
- package/dist/credentials.js +66 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +30 -0
- package/dist/gen/gitd/v1/gitd_pb.d.ts +3023 -0
- package/dist/gen/gitd/v1/gitd_pb.js +826 -0
- package/dist/gitd.d.ts +65 -0
- package/dist/gitd.js +206 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +5 -0
- package/dist/publishing-convert.d.ts +8 -0
- package/dist/publishing-convert.js +74 -0
- package/dist/publishing-types.d.ts +70 -0
- package/dist/publishing-types.js +1 -0
- package/dist/publishing.d.ts +23 -0
- package/dist/publishing.js +40 -0
- package/dist/types.d.ts +232 -0
- package/dist/types.js +1 -0
- package/dist/warm-convert.d.ts +4 -0
- package/dist/warm-convert.js +12 -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,6 @@
|
|
|
1
|
+
import type { MessageInitShape } from "@bufbuild/protobuf";
|
|
2
|
+
import { type Side as SideMessage, type SideSchema, type WatchResponse } from "./gen/gitd/v1/gitd_pb.js";
|
|
3
|
+
import type { Side, WorkspaceEvent } from "./types.js";
|
|
4
|
+
export declare function sideProto(side: Side): MessageInitShape<typeof SideSchema>;
|
|
5
|
+
export declare function toSide(side: SideMessage | undefined): Side;
|
|
6
|
+
export declare function toEvent(event: WatchResponse): WorkspaceEvent;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
2
|
+
import { WatchResponse_Kind, } from "./gen/gitd/v1/gitd_pb.js";
|
|
3
|
+
export function sideProto(side) {
|
|
4
|
+
return "workspace" in side
|
|
5
|
+
? { of: { case: "workspace", value: side.workspace } }
|
|
6
|
+
: { of: { case: "branch", value: side.branch } };
|
|
7
|
+
}
|
|
8
|
+
export function toSide(side) {
|
|
9
|
+
return side?.of.case === "workspace" ? { workspace: side.of.value } : { branch: side?.of.value ?? "" };
|
|
10
|
+
}
|
|
11
|
+
export function toEvent(event) {
|
|
12
|
+
return {
|
|
13
|
+
kind: eventKind(event.kind),
|
|
14
|
+
path: event.path,
|
|
15
|
+
time: event.time ? timestampDate(event.time) : new Date(0),
|
|
16
|
+
branch: event.branch,
|
|
17
|
+
detail: event.detail,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function eventKind(kind) {
|
|
21
|
+
switch (kind) {
|
|
22
|
+
case WatchResponse_Kind.MADE:
|
|
23
|
+
return "made";
|
|
24
|
+
case WatchResponse_Kind.PUBLISHED:
|
|
25
|
+
return "published";
|
|
26
|
+
case WatchResponse_Kind.REFUSED:
|
|
27
|
+
return "refused";
|
|
28
|
+
case WatchResponse_Kind.REMOVED:
|
|
29
|
+
return "removed";
|
|
30
|
+
default:
|
|
31
|
+
return "changed";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MessageInitShape } from "@bufbuild/protobuf";
|
|
2
|
+
import { ProbeResponse_Access, Strategy, type AuditEntry as AuditEntryMessage, type ChangedFile as ChangedFileMessage, type ChangesResponse, type PublishResponse, type PublishedRepository as PublishedRepositoryMessage, type RevisionSchema, type WorkspaceResult } from "./gen/gitd/v1/gitd_pb.js";
|
|
3
|
+
import type { AuditEntry, ChangedFile, Changes, Probe, Published, 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
|
+
/** What a publish pushed, and the pull request it opened or why it could not. */
|
|
13
|
+
export declare function publishedOf(answer: PublishResponse): Published;
|
|
14
|
+
export declare function toAuditEntry(entry: AuditEntryMessage): AuditEntry;
|
|
15
|
+
export declare function toChanges(answer: ChangesResponse): Changes;
|
|
16
|
+
export declare function toChangedFiles(files: ChangedFileMessage[]): ChangedFile[];
|
package/dist/convert.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
2
|
+
import { ChangedFile_Kind, ConfigurationChange_Action, 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, warm: layers.warm },
|
|
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
|
+
/** What a publish pushed, and the pull request it opened or why it could not. */
|
|
68
|
+
export function publishedOf(answer) {
|
|
69
|
+
return {
|
|
70
|
+
branch: answer.branch,
|
|
71
|
+
repositories: pushedOf(answer.repository, answer.submodules),
|
|
72
|
+
pullRequest: openedOf(answer.pullRequest),
|
|
73
|
+
pullRequestRefused: refusalOf(answer.pullRequestFailure),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function toAuditEntry(entry) {
|
|
77
|
+
const change = entry.change;
|
|
78
|
+
return {
|
|
79
|
+
time: entry.time ? timestampDate(entry.time) : new Date(0),
|
|
80
|
+
workspace: entry.workspace,
|
|
81
|
+
url: entry.url,
|
|
82
|
+
branch: entry.branch,
|
|
83
|
+
repositories: pushedOf(entry.repository, entry.submodules),
|
|
84
|
+
refused: refusalOf(entry.refusal),
|
|
85
|
+
requestedBy: entry.requestedBy,
|
|
86
|
+
pullRequest: openedOf(entry.pullRequest),
|
|
87
|
+
pullRequestRefused: refusalOf(entry.pullRequestFailure),
|
|
88
|
+
provenance: entry.provenance && {
|
|
89
|
+
agent: entry.provenance.agent,
|
|
90
|
+
model: entry.provenance.model,
|
|
91
|
+
session: entry.provenance.session,
|
|
92
|
+
taskUrl: entry.provenance.taskUrl,
|
|
93
|
+
},
|
|
94
|
+
change: change && {
|
|
95
|
+
action: (ConfigurationChange_Action[change.action] ?? "").replace("ACTION_", "").toLowerCase(),
|
|
96
|
+
subject: change.subject,
|
|
97
|
+
urlPrefix: change.urlPrefix,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function openedOf(opened) {
|
|
102
|
+
return opened && { url: opened.url, number: Number(opened.number) };
|
|
103
|
+
}
|
|
104
|
+
function refusalOf(failure) {
|
|
105
|
+
return failure && { reason: (GitFailure_Reason[failure.reason] ?? "").replace("REASON_", ""), said: failure.gitSaid };
|
|
106
|
+
}
|
|
107
|
+
export function toChanges(answer) {
|
|
108
|
+
return { base: answer.base, fromLayer: answer.fromLayer, files: toChangedFiles(answer.files) };
|
|
109
|
+
}
|
|
110
|
+
export function toChangedFiles(files) {
|
|
111
|
+
return files.map((file) => ({
|
|
112
|
+
path: file.path,
|
|
113
|
+
kind: kindName(file.kind),
|
|
114
|
+
addedLines: file.addedLines,
|
|
115
|
+
removedLines: file.removedLines,
|
|
116
|
+
binary: file.binary,
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
function kindName(kind) {
|
|
120
|
+
switch (kind) {
|
|
121
|
+
case ChangedFile_Kind.ADDED:
|
|
122
|
+
return "added";
|
|
123
|
+
case ChangedFile_Kind.DELETED:
|
|
124
|
+
return "deleted";
|
|
125
|
+
default:
|
|
126
|
+
return "modified";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
2
|
+
import { Connection_Kind } from "./gen/gitd/v1/gitd_pb.js";
|
|
3
|
+
export function toKeptCredential(connection) {
|
|
4
|
+
return {
|
|
5
|
+
id: connection?.id ?? "",
|
|
6
|
+
kind: kindName(connection?.kind ?? Connection_Kind.UNSPECIFIED),
|
|
7
|
+
urlPrefix: connection?.urlPrefix ?? "",
|
|
8
|
+
actsAs: connection?.actsAs ?? "",
|
|
9
|
+
appId: Number(connection?.appId ?? 0),
|
|
10
|
+
manageUrl: connection?.manageUrl ?? "",
|
|
11
|
+
expires: connection?.expires && timestampDate(connection.expires),
|
|
12
|
+
fromFile: connection?.fromFile ?? false,
|
|
13
|
+
created: connection?.created && timestampDate(connection.created),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function kindName(kind) {
|
|
17
|
+
switch (kind) {
|
|
18
|
+
case Connection_Kind.SSH_KEY:
|
|
19
|
+
return "ssh_key";
|
|
20
|
+
case Connection_Kind.GITHUB_APP:
|
|
21
|
+
return "github_app";
|
|
22
|
+
case Connection_Kind.GITHUB_LOGIN:
|
|
23
|
+
return "github_login";
|
|
24
|
+
default:
|
|
25
|
+
return "token";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** What a credential gitd keeps is: a token, an SSH key, a GitHub App, or a person signed in. */
|
|
2
|
+
export type CredentialKind = "token" | "ssh_key" | "github_app" | "github_login";
|
|
3
|
+
/** One credential gitd keeps, never its secret. */
|
|
4
|
+
export interface KeptCredential {
|
|
5
|
+
/** What to remove it by. */
|
|
6
|
+
id: string;
|
|
7
|
+
kind: CredentialKind;
|
|
8
|
+
/** The repositories it opens: those whose address starts with this. */
|
|
9
|
+
urlPrefix: string;
|
|
10
|
+
/** Whom it acts as: an App's bot (`acme-agents[bot]`) or a person. */
|
|
11
|
+
actsAs: string;
|
|
12
|
+
appId: number;
|
|
13
|
+
/** Where an App's installations are managed. */
|
|
14
|
+
manageUrl: string;
|
|
15
|
+
/** When a sign-in stops renewing. */
|
|
16
|
+
expires?: Date;
|
|
17
|
+
/** Declared in gitd.toml: shown, never changed through the API. */
|
|
18
|
+
fromFile: boolean;
|
|
19
|
+
created?: Date;
|
|
20
|
+
}
|
|
21
|
+
/** A GitHub App or sign-in gitd now keeps, and where it is installed on repositories. */
|
|
22
|
+
export interface Connected {
|
|
23
|
+
credential: KeptCredential;
|
|
24
|
+
installUrl: string;
|
|
25
|
+
}
|
|
26
|
+
/** What a browser posts to GitHub to create an App: `manifest`, as a form field, to `formAction`. */
|
|
27
|
+
export interface AppToCreate {
|
|
28
|
+
formAction: string;
|
|
29
|
+
manifest: string;
|
|
30
|
+
/** Sent back by GitHub with the code, for completeGitHubApp. */
|
|
31
|
+
state: string;
|
|
32
|
+
}
|
|
33
|
+
/** A sign-in waiting for a person to enter `userCode` at `verificationUrl`. */
|
|
34
|
+
export interface SignIn {
|
|
35
|
+
userCode: string;
|
|
36
|
+
verificationUrl: string;
|
|
37
|
+
loginId: string;
|
|
38
|
+
expiresInSeconds: number;
|
|
39
|
+
}
|
|
40
|
+
export interface BeginGitHubAppOptions {
|
|
41
|
+
/** The App's name on GitHub; its bot is `<name>[bot]`. */
|
|
42
|
+
name: string;
|
|
43
|
+
/** Where GitHub sends the person back with a code and the state. */
|
|
44
|
+
redirectUrl: string;
|
|
45
|
+
/** The organisation that owns it; default: the person creating it. */
|
|
46
|
+
organization?: string;
|
|
47
|
+
/** A GitHub Enterprise Server's host; default github.com. */
|
|
48
|
+
host?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface AddGitHubAppOptions {
|
|
51
|
+
appId: number;
|
|
52
|
+
/** The App's private key, in PEM. */
|
|
53
|
+
privateKey: string;
|
|
54
|
+
host?: string;
|
|
55
|
+
/** The repositories it opens; default every one on the host. */
|
|
56
|
+
urlPrefix?: string;
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Client } from "@connectrpc/connect";
|
|
2
|
+
import type { ConnectionService } from "./gen/gitd/v1/gitd_pb.js";
|
|
3
|
+
import type { AddGitHubAppOptions, AppToCreate, BeginGitHubAppOptions, Connected, KeptCredential, SignIn } from "./credential-types.js";
|
|
4
|
+
/**
|
|
5
|
+
* The credentials gitd keeps, as `gitd.credentials`: for a platform's own pages, never for
|
|
6
|
+
* an agent.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Credentials {
|
|
9
|
+
private readonly connections;
|
|
10
|
+
constructor(connections: Client<typeof ConnectionService>);
|
|
11
|
+
/** Every credential gitd keeps, gitd.toml's included, never a secret. */
|
|
12
|
+
list(): Promise<KeptCredential[]>;
|
|
13
|
+
/** Forgets a credential kept through the API. */
|
|
14
|
+
remove(id: string): Promise<void>;
|
|
15
|
+
/** Keeps a token for the repositories under urlPrefix, which names the host. */
|
|
16
|
+
addToken(urlPrefix: string, token: string, user?: string): Promise<KeptCredential>;
|
|
17
|
+
/** Keeps a GitHub App a platform already has, once GitHub takes the key as its own. */
|
|
18
|
+
addGitHubApp(options: AddGitHubAppOptions): Promise<KeptCredential>;
|
|
19
|
+
/** Starts creating the organisation's own GitHub App from a manifest. */
|
|
20
|
+
beginGitHubApp(options: BeginGitHubAppOptions): Promise<AppToCreate>;
|
|
21
|
+
/** Finishes creating the App with the code and state GitHub sent back. */
|
|
22
|
+
completeGitHubApp(code: string, state: string): Promise<Connected>;
|
|
23
|
+
/** Asks GitHub for the code a person enters to sign in through gitd's public App. */
|
|
24
|
+
startGitHubLogin(host?: string): Promise<SignIn>;
|
|
25
|
+
/** Waits until the person has entered the code, and keeps the sign-in. */
|
|
26
|
+
finishGitHubLogin(loginId: string): Promise<Connected>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { call } from "./calls.js";
|
|
2
|
+
import { toKeptCredential } from "./credential-convert.js";
|
|
3
|
+
/**
|
|
4
|
+
* The credentials gitd keeps, as `gitd.credentials`: for a platform's own pages, never for
|
|
5
|
+
* an agent.
|
|
6
|
+
*/
|
|
7
|
+
export class Credentials {
|
|
8
|
+
connections;
|
|
9
|
+
constructor(connections) {
|
|
10
|
+
this.connections = connections;
|
|
11
|
+
}
|
|
12
|
+
/** Every credential gitd keeps, gitd.toml's included, never a secret. */
|
|
13
|
+
async list() {
|
|
14
|
+
const answer = await call(() => this.connections.listConnections({}));
|
|
15
|
+
return answer.connections.map(toKeptCredential);
|
|
16
|
+
}
|
|
17
|
+
/** Forgets a credential kept through the API. */
|
|
18
|
+
async remove(id) {
|
|
19
|
+
await call(() => this.connections.removeConnection({ id }));
|
|
20
|
+
}
|
|
21
|
+
/** Keeps a token for the repositories under urlPrefix, which names the host. */
|
|
22
|
+
async addToken(urlPrefix, token, user = "") {
|
|
23
|
+
const answer = await call(() => this.connections.addToken({ urlPrefix, token, user }));
|
|
24
|
+
return toKeptCredential(answer.connection);
|
|
25
|
+
}
|
|
26
|
+
/** Keeps a GitHub App a platform already has, once GitHub takes the key as its own. */
|
|
27
|
+
async addGitHubApp(options) {
|
|
28
|
+
const answer = await call(() => this.connections.addGitHubApp({
|
|
29
|
+
appId: BigInt(options.appId),
|
|
30
|
+
privateKey: options.privateKey,
|
|
31
|
+
host: options.host ?? "",
|
|
32
|
+
urlPrefix: options.urlPrefix ?? "",
|
|
33
|
+
}));
|
|
34
|
+
return toKeptCredential(answer.connection);
|
|
35
|
+
}
|
|
36
|
+
/** Starts creating the organisation's own GitHub App from a manifest. */
|
|
37
|
+
async beginGitHubApp(options) {
|
|
38
|
+
const answer = await call(() => this.connections.beginGitHubApp({
|
|
39
|
+
name: options.name,
|
|
40
|
+
redirectUrl: options.redirectUrl,
|
|
41
|
+
organization: options.organization ?? "",
|
|
42
|
+
host: options.host ?? "",
|
|
43
|
+
}));
|
|
44
|
+
return { formAction: answer.formAction, manifest: answer.manifest, state: answer.state };
|
|
45
|
+
}
|
|
46
|
+
/** Finishes creating the App with the code and state GitHub sent back. */
|
|
47
|
+
async completeGitHubApp(code, state) {
|
|
48
|
+
const answer = await call(() => this.connections.completeGitHubApp({ code, state }));
|
|
49
|
+
return { credential: toKeptCredential(answer.connection), installUrl: answer.installUrl };
|
|
50
|
+
}
|
|
51
|
+
/** Asks GitHub for the code a person enters to sign in through gitd's public App. */
|
|
52
|
+
async startGitHubLogin(host = "") {
|
|
53
|
+
const answer = await call(() => this.connections.startGitHubLogin({ host }));
|
|
54
|
+
return {
|
|
55
|
+
userCode: answer.userCode,
|
|
56
|
+
verificationUrl: answer.verificationUrl,
|
|
57
|
+
loginId: answer.loginId,
|
|
58
|
+
expiresInSeconds: answer.expiresInSeconds,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Waits until the person has entered the code, and keeps the sign-in. */
|
|
62
|
+
async finishGitHubLogin(loginId) {
|
|
63
|
+
const answer = await call(() => this.connections.finishGitHubLogin({ loginId }));
|
|
64
|
+
return { credential: toKeptCredential(answer.connection), installUrl: answer.installUrl };
|
|
65
|
+
}
|
|
66
|
+
}
|
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
|
+
}
|