repo-sdk 0.2.0 → 0.2.2
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 +142 -19
- package/dist/github.d.ts +31 -1
- package/dist/github.js +95 -45
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,16 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/repo-sdk)
|
|
5
5
|
[](https://github.com/capawesome-team/repo-sdk/blob/main/LICENSE)
|
|
6
6
|
|
|
7
|
-
A unified, normalized, zero-dependency, edge-compatible TypeScript SDK over GitHub, GitLab, Bitbucket Cloud, Azure DevOps, and Gitea — plus generic git smart-HTTP remotes. Write your
|
|
7
|
+
A unified, normalized, zero-dependency, edge-compatible TypeScript SDK over [GitHub](https://github.com), [GitLab](https://gitlab.com), [Bitbucket Cloud](https://bitbucket.org), [Azure DevOps](https://azure.microsoft.com/products/devops), and [Gitea](https://about.gitea.com) — plus generic git smart-HTTP remotes. Write your repository, commit, branch, tag, ref-resolution, download and webhook logic once against one normalized API — built on raw `fetch` and Web Crypto, so it runs on Node, Cloudflare Workers, and other Web-standard runtimes.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **One API, eight namespaces** — users, namespaces, repos, commits, branches, tags, refs, and webhooks behind a single interface.
|
|
12
|
+
- **Capability gating** — providers differ. The client reports what the active provider supports and throws `unsupported` instead of silently dropping an option.
|
|
13
|
+
- **Ref resolution that follows git** — `refs.resolve` accepts a branch, a tag, a fully-qualified `refs/…`, `HEAD`, or an abbreviated SHA and returns one normalized match, with tags shadowing branches exactly like `git rev-parse`.
|
|
14
|
+
- **Webhook verify & parse** — standalone per-provider helpers that take a Web-standard `Request`. No client, no credentials. `detectWebhookProvider` routes a shared endpoint to the right pair.
|
|
15
|
+
- **Zero dependencies** — `fetch` and Web Crypto only. No `node:*` imports, so it runs on Cloudflare Workers without `nodejs_compat` — including RS256 JWT signing for GitHub App auth.
|
|
16
|
+
- **Tree-shakable and typed** — each provider ships on its own subpath, so unused providers never reach your bundle. Every model is normalized (IDs as strings, dates as `Date`, refs fully qualified) and keeps the untouched provider payload on `raw`.
|
|
8
17
|
|
|
9
18
|
## Installation
|
|
10
19
|
|
|
@@ -14,6 +23,8 @@ npm install repo-sdk
|
|
|
14
23
|
|
|
15
24
|
## Quickstart
|
|
16
25
|
|
|
26
|
+
Create a client from the package root and a provider factory from its subpath.
|
|
27
|
+
|
|
17
28
|
```ts
|
|
18
29
|
import { createClient } from 'repo-sdk';
|
|
19
30
|
import { github } from 'repo-sdk/github';
|
|
@@ -21,42 +32,154 @@ import { github } from 'repo-sdk/github';
|
|
|
21
32
|
const client = createClient({
|
|
22
33
|
provider: github({ auth: { token: process.env.GITHUB_TOKEN! } }),
|
|
23
34
|
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Resolve a ref — a branch, tag, SHA, or `HEAD` — to a single commit, and page through history.
|
|
24
38
|
|
|
25
|
-
|
|
39
|
+
```ts
|
|
26
40
|
const head = await client.commits.get({ repo: 'capawesome-team/repo-sdk', ref: 'main' });
|
|
27
41
|
console.log(head.sha, head.message);
|
|
28
42
|
|
|
29
|
-
|
|
30
|
-
const { data: commits } = await client.commits.list({
|
|
43
|
+
const { data: commits, cursor } = await client.commits.list({
|
|
31
44
|
repo: 'capawesome-team/repo-sdk',
|
|
32
45
|
ref: 'main',
|
|
33
46
|
limit: 20,
|
|
34
47
|
});
|
|
35
48
|
```
|
|
36
49
|
|
|
37
|
-
|
|
50
|
+
Find out what a user-supplied ref actually is before you act on it.
|
|
38
51
|
|
|
39
|
-
|
|
52
|
+
```ts
|
|
53
|
+
const match = await client.refs.resolve({ repo: 'capawesome-team/repo-sdk', ref: 'v1.0.0' });
|
|
54
|
+
match.type; // 'branch' | 'tag' | 'commit'
|
|
55
|
+
match.ref; // 'refs/tags/v1.0.0'
|
|
56
|
+
match.sha; // peeled to the commit SHA for annotated tags
|
|
57
|
+
```
|
|
40
58
|
|
|
41
|
-
|
|
59
|
+
Download the code at that ref, as a stream or as a credential-bearing clone URL for the `git` CLI.
|
|
42
60
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
-
|
|
46
|
-
|
|
61
|
+
```ts
|
|
62
|
+
const archive = await client.repos.downloadArchive({
|
|
63
|
+
repo: 'capawesome-team/repo-sdk',
|
|
64
|
+
ref: match.sha,
|
|
65
|
+
format: 'tar.gz',
|
|
66
|
+
});
|
|
67
|
+
await new Response(archive.stream).arrayBuffer();
|
|
68
|
+
|
|
69
|
+
const { url } = await client.repos.getCloneUrl({ repo: 'capawesome-team/repo-sdk' });
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Verify and handle the webhook. The helpers take the `Request` itself — no client, no credentials.
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { parseWebhookEvent, verifyWebhook } from 'repo-sdk/github';
|
|
76
|
+
|
|
77
|
+
export async function POST(request: Request): Promise<Response> {
|
|
78
|
+
if (!(await verifyWebhook({ request, secret: process.env.WEBHOOK_SECRET! }))) {
|
|
79
|
+
return new Response('invalid signature', { status: 401 });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const event = await parseWebhookEvent(request);
|
|
83
|
+
|
|
84
|
+
switch (event.type) {
|
|
85
|
+
case 'push':
|
|
86
|
+
// event.headCommitSha is undefined when the push deleted the ref.
|
|
87
|
+
break;
|
|
88
|
+
case 'tag_push':
|
|
89
|
+
// event.ref is always fully qualified — refs/tags/v1.0.0.
|
|
90
|
+
break;
|
|
91
|
+
case 'release':
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return new Response(null, { status: 204 });
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## API at a glance
|
|
100
|
+
|
|
101
|
+
| Namespace | Methods |
|
|
102
|
+
| ------------ | -------------------------------------------------------------- |
|
|
103
|
+
| `users` | `me` |
|
|
104
|
+
| `namespaces` | `list` · `listAll` |
|
|
105
|
+
| `repos` | `list` · `listAll` · `get` · `downloadArchive` · `getCloneUrl` |
|
|
106
|
+
| `commits` | `list` · `listAll` · `get` |
|
|
107
|
+
| `branches` | `list` · `listAll` · `get` |
|
|
108
|
+
| `tags` | `list` · `listAll` · `get` |
|
|
109
|
+
| `refs` | `resolve` · `search` |
|
|
110
|
+
| `webhooks` | `create` · `list` · `get` · `update` · `delete` |
|
|
111
|
+
|
|
112
|
+
Every list returns an opaque cursor and has a `listAll` async generator that walks the pages for you. Cursors are provider-tagged and origin-checked, so a forged one cannot redirect an authenticated request. Every method accepts a `signal` for cancellation. Rate-limited requests are retried once when the provider's `Retry-After` fits the budget (10 seconds by default, configurable via `retry`).
|
|
113
|
+
|
|
114
|
+
Webhook payload helpers — `verifyWebhook` and `parseWebhookEvent` — are exported from the provider subpaths and take no credential beyond the shared secret, so they run in a handler with no client at all. `detectWebhookProvider` from the core entry identifies the sender when one endpoint serves several providers.
|
|
47
115
|
|
|
48
116
|
## Providers
|
|
49
117
|
|
|
50
118
|
| Provider | Import | Notes |
|
|
51
119
|
| ------------ | ----------------------- | ---------------------------------------------------------------- |
|
|
52
|
-
| GitHub | `repo-sdk/github` |
|
|
53
|
-
| GitLab | `repo-sdk/gitlab` |
|
|
54
|
-
| Bitbucket | `repo-sdk/bitbucket` | Bitbucket Cloud
|
|
55
|
-
| Azure DevOps | `repo-sdk/azure-devops` | PAT or Entra ID
|
|
56
|
-
| Gitea | `repo-sdk/gitea` | Gitea ≥ 1.20 and Forgejo
|
|
57
|
-
| Generic git | `repo-sdk/git-http` | Any git smart-HTTP remote
|
|
120
|
+
| GitHub | `repo-sdk/github` | Personal access token or GitHub App installation auth. |
|
|
121
|
+
| GitLab | `repo-sdk/gitlab` | Personal, group, project, or OAuth token. |
|
|
122
|
+
| Bitbucket | `repo-sdk/bitbucket` | Bitbucket Cloud. Atlassian API token or access token. |
|
|
123
|
+
| Azure DevOps | `repo-sdk/azure-devops` | Organization-scoped. PAT or Entra ID token. |
|
|
124
|
+
| Gitea | `repo-sdk/gitea` | Gitea ≥ 1.20 and Forgejo. |
|
|
125
|
+
| Generic git | `repo-sdk/git-http` | Any git smart-HTTP remote. Ref discovery and clone URLs only. |
|
|
126
|
+
| Testing | `repo-sdk/testing` | Seedable in-memory provider for your test suite. No credentials. |
|
|
58
127
|
|
|
59
|
-
Self-hosted deployments — GitHub Enterprise Server, GitLab self-managed, Azure DevOps Server, and Gitea/Forgejo
|
|
128
|
+
Every factory accepts an injectable `fetch`, and every token can be a static string or a `tokenProvider` that mints one per request and is re-invoked once on a 401. Self-hosted deployments — GitHub Enterprise Server, GitLab self-managed, Azure DevOps Server, and Gitea/Forgejo — are supported via `baseUrl`. Providers do not support the same feature set — `client.capabilities` tells you what the active one can do, and the full breakdown lives in the [capability matrix](https://repo-sdk.dev/docs/reference/capability-matrix).
|
|
129
|
+
|
|
130
|
+
## Testing
|
|
131
|
+
|
|
132
|
+
`repo-sdk/testing` ships a seedable in-memory provider you hand to `createClient` like any other — no account, no tokens, no `fetch` stubs, no throwaway repositories.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { createClient } from 'repo-sdk';
|
|
136
|
+
import { createInMemoryProvider } from 'repo-sdk/testing';
|
|
137
|
+
|
|
138
|
+
const client = createClient({
|
|
139
|
+
provider: createInMemoryProvider({
|
|
140
|
+
repositories: { 'acme/app': { name: 'app', namespace: 'acme', defaultBranch: 'main' } },
|
|
141
|
+
branches: { 'acme/app': [{ name: 'main', sha: 'c1' }] },
|
|
142
|
+
commits: { 'acme/app': [{ sha: 'c1', message: 'Initial commit' }] },
|
|
143
|
+
}),
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
It reports every capability enabled by default, and a second options argument simulates a specific provider's identity and capability gaps — so code that branches on `client.capabilities` is testable. Its page size is deliberately small, so your cursor pagination gets exercised, and `provider.state` exposes the data as plain maps for assertions.
|
|
148
|
+
|
|
149
|
+
## Errors
|
|
150
|
+
|
|
151
|
+
Every failure is a `RepoError` with a closed `code` union: `unauthorized`, `forbidden`, `not_found`, `rate_limited`, `validation`, `unsupported`, `provider_error`, `network_error`.
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { RepoError } from 'repo-sdk';
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
await client.repos.downloadArchive({ repo: 'acme/app', ref: 'main', format: 'tar.gz' });
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if (error instanceof RepoError && error.code === 'unsupported') {
|
|
160
|
+
// Azure DevOps serves zip only — fall back instead of failing the job.
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Errors also carry `provider`, `status`, `retryAfter` and `retryable`; `cause` is the underlying JS `Error`. Token values are redacted from every message, so a leaked stack trace cannot leak a credential. The one deliberate exception is `repos.getCloneUrl`, which returns a URL with the credential embedded because that is what `git clone` needs — treat it as a secret.
|
|
166
|
+
|
|
167
|
+
## Runtime support
|
|
168
|
+
|
|
169
|
+
Node.js ≥ 20, Cloudflare Workers (without `nodejs_compat`), Deno, Bun, Vercel Edge, and any other runtime with `fetch`, Web Crypto and `TextEncoder`. ESM only.
|
|
170
|
+
|
|
171
|
+
## Documentation
|
|
172
|
+
|
|
173
|
+
Full documentation lives at **[repo-sdk.dev](https://repo-sdk.dev)** ([docs](https://repo-sdk.dev/docs)):
|
|
174
|
+
|
|
175
|
+
- [Quickstart](https://repo-sdk.dev/docs/quickstart)
|
|
176
|
+
- [Concepts](https://repo-sdk.dev/docs/concepts/client-and-providers) — clients, repositories and namespaces, capabilities, pagination, errors
|
|
177
|
+
- [Authentication](https://repo-sdk.dev/docs/authentication) — tokens, GitHub Apps, Entra ID, and the scopes each provider needs
|
|
178
|
+
- [Guides](https://repo-sdk.dev/docs/guides/resolving-refs) — resolving refs, commits, branches, tags, downloading code, managing and receiving webhooks
|
|
179
|
+
- [Testing](https://repo-sdk.dev/docs/testing)
|
|
180
|
+
- [Capability matrix](https://repo-sdk.dev/docs/reference/capability-matrix)
|
|
181
|
+
- [Provider support](https://repo-sdk.dev/docs/reference/provider-support)
|
|
182
|
+
- [Error codes](https://repo-sdk.dev/docs/reference/error-codes)
|
|
60
183
|
|
|
61
184
|
## Development
|
|
62
185
|
|
|
@@ -69,7 +192,7 @@ npm install
|
|
|
69
192
|
| Script | Description |
|
|
70
193
|
| -------------------- | --------------------------------------------------------------- |
|
|
71
194
|
| `npm run build` | Build with tsdown |
|
|
72
|
-
| `npm test` | Run the unit + contract test suite
|
|
195
|
+
| `npm test` | Run the unit + provider contract test suite |
|
|
73
196
|
| `npm run test:watch` | Run tests in watch mode |
|
|
74
197
|
| `npm run test:live` | Gated live provider tests (env vars documented in CONTRIBUTING) |
|
|
75
198
|
| `npm run typecheck` | Type-check without emitting |
|
package/dist/github.d.ts
CHANGED
|
@@ -57,6 +57,18 @@ interface ListUserInstallationsParams {
|
|
|
57
57
|
cursor?: string;
|
|
58
58
|
signal?: AbortSignal;
|
|
59
59
|
}
|
|
60
|
+
interface ListInstallationRequestsParams {
|
|
61
|
+
/** The GitHub App id the installation requests belong to. */
|
|
62
|
+
appId: string | number;
|
|
63
|
+
/** PEM-encoded private key of the app, used to sign the app JWT. */
|
|
64
|
+
privateKey: string;
|
|
65
|
+
/** GHES API root (`/api/v3`); defaults to `https://api.github.com`. */
|
|
66
|
+
baseUrl?: string;
|
|
67
|
+
fetch?: typeof fetch;
|
|
68
|
+
limit?: number;
|
|
69
|
+
cursor?: string;
|
|
70
|
+
signal?: AbortSignal;
|
|
71
|
+
}
|
|
60
72
|
interface GitHubInstallationAccount {
|
|
61
73
|
id: string;
|
|
62
74
|
login: string;
|
|
@@ -69,6 +81,16 @@ interface GitHubUserInstallation {
|
|
|
69
81
|
account?: GitHubInstallationAccount;
|
|
70
82
|
raw: unknown;
|
|
71
83
|
}
|
|
84
|
+
interface GitHubInstallationRequest {
|
|
85
|
+
id: string;
|
|
86
|
+
/** Account (organization or user) the installation was requested for. */
|
|
87
|
+
account?: GitHubInstallationAccount;
|
|
88
|
+
/** ISO 8601 timestamp of when the request was created. */
|
|
89
|
+
createdAt: string;
|
|
90
|
+
/** User who requested the installation. */
|
|
91
|
+
requester?: GitHubInstallationAccount;
|
|
92
|
+
raw: unknown;
|
|
93
|
+
}
|
|
72
94
|
/**
|
|
73
95
|
* Lists the GitHub App installations the given user can access
|
|
74
96
|
* (`GET /user/installations`). Standalone by design: the endpoint requires a
|
|
@@ -76,9 +98,17 @@ interface GitHubUserInstallation {
|
|
|
76
98
|
* `github` provider authenticates its requests with.
|
|
77
99
|
*/
|
|
78
100
|
declare function listUserInstallations(params: ListUserInstallationsParams): Promise<Page<GitHubUserInstallation>>;
|
|
101
|
+
/**
|
|
102
|
+
* Lists the pending installation requests of a GitHub App
|
|
103
|
+
* (`GET /app/installation-requests`) — installations an organization owner has
|
|
104
|
+
* yet to approve. Standalone by design: the endpoint is authenticated with an
|
|
105
|
+
* app JWT, a different credential from the installation token the `github`
|
|
106
|
+
* provider authenticates its requests with.
|
|
107
|
+
*/
|
|
108
|
+
declare function listInstallationRequests(params: ListInstallationRequestsParams): Promise<Page<GitHubInstallationRequest>>;
|
|
79
109
|
//#endregion
|
|
80
110
|
//#region src/providers/github/webhooks.d.ts
|
|
81
111
|
declare function verifyWebhook(params: VerifyWebhookParams): Promise<boolean>;
|
|
82
112
|
declare function parseWebhookEvent(input: Request | IncomingWebhookRequest): Promise<ParsedWebhookEvent>;
|
|
83
113
|
//#endregion
|
|
84
|
-
export { type GitHubAppAuth, type GitHubAuth, type GitHubInstallationAccount, type GitHubInstallationToken, type GitHubProviderOptions, type GitHubRepoProvider, type GitHubTokenAuth, type GitHubUserInstallation, type ListUserInstallationsParams, type VerifyWebhookParams, commitWebUrl, github, listUserInstallations, parseWebhookEvent, verifyWebhook };
|
|
114
|
+
export { type GitHubAppAuth, type GitHubAuth, type GitHubInstallationAccount, type GitHubInstallationRequest, type GitHubInstallationToken, type GitHubProviderOptions, type GitHubRepoProvider, type GitHubTokenAuth, type GitHubUserInstallation, type ListInstallationRequestsParams, type ListUserInstallationsParams, type VerifyWebhookParams, commitWebUrl, github, listInstallationRequests, listUserInstallations, parseWebhookEvent, verifyWebhook };
|
package/dist/github.js
CHANGED
|
@@ -77,7 +77,23 @@ const encoder = new TextEncoder();
|
|
|
77
77
|
function base64UrlJson(value) {
|
|
78
78
|
return bytesToBase64Url(encoder.encode(JSON.stringify(value)));
|
|
79
79
|
}
|
|
80
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Mints a short-lived RS256 JWT signed with the app private key — the
|
|
82
|
+
* credential for app-level endpoints (`/app/…`), which installation and user
|
|
83
|
+
* tokens cannot access. Internal to the package: not exported from the
|
|
84
|
+
* `repo-sdk/github` barrel.
|
|
85
|
+
*/
|
|
86
|
+
async function createAppJwt(options) {
|
|
87
|
+
let key;
|
|
88
|
+
try {
|
|
89
|
+
key = await importPrivateKey(options.privateKey);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
throw new RepoError("GitHub App private key is not a valid PEM-encoded RSA private key", {
|
|
92
|
+
provider: "github",
|
|
93
|
+
code: "validation",
|
|
94
|
+
cause: error
|
|
95
|
+
});
|
|
96
|
+
}
|
|
81
97
|
const now = Math.floor(Date.now() / 1e3);
|
|
82
98
|
const signingInput = `${base64UrlJson({
|
|
83
99
|
alg: "RS256",
|
|
@@ -85,7 +101,7 @@ async function createJwt(key, appId) {
|
|
|
85
101
|
})}.${base64UrlJson({
|
|
86
102
|
iat: now - 60,
|
|
87
103
|
exp: now + 540,
|
|
88
|
-
iss: appId
|
|
104
|
+
iss: String(options.appId)
|
|
89
105
|
})}`;
|
|
90
106
|
const signature = await crypto.subtle.sign("RSASSA-PKCS1-v1_5", key, encoder.encode(signingInput));
|
|
91
107
|
return `${signingInput}.${bytesToBase64Url(new Uint8Array(signature))}`;
|
|
@@ -94,8 +110,9 @@ async function createJwt(key, appId) {
|
|
|
94
110
|
* Mints and caches GitHub App installation access tokens. A short-lived RS256
|
|
95
111
|
* JWT (signed with the app private key) authenticates the app-level calls that
|
|
96
112
|
* resolve the installation and mint installation tokens; the installation token
|
|
97
|
-
* is cached and refreshed once it nears expiry. A fresh
|
|
98
|
-
* refresh (cheap, and refreshes are rare given the ~1h token
|
|
113
|
+
* is cached and refreshed once it nears expiry. A fresh key import and JWT go
|
|
114
|
+
* into every token refresh (cheap, and refreshes are rare given the ~1h token
|
|
115
|
+
* TTL).
|
|
99
116
|
*/
|
|
100
117
|
var AppTokenSource = class {
|
|
101
118
|
kind = "app";
|
|
@@ -107,7 +124,6 @@ var AppTokenSource = class {
|
|
|
107
124
|
userAgent;
|
|
108
125
|
installationId;
|
|
109
126
|
owner;
|
|
110
|
-
keyPromise;
|
|
111
127
|
cached;
|
|
112
128
|
refresh;
|
|
113
129
|
constructor(options) {
|
|
@@ -153,7 +169,10 @@ var AppTokenSource = class {
|
|
|
153
169
|
return this.refresh;
|
|
154
170
|
}
|
|
155
171
|
async mintInstallationToken() {
|
|
156
|
-
const jwt = await
|
|
172
|
+
const jwt = await createAppJwt({
|
|
173
|
+
appId: this.appId,
|
|
174
|
+
privateKey: this.privateKey
|
|
175
|
+
});
|
|
157
176
|
const installationId = await this.resolveInstallationId(jwt);
|
|
158
177
|
const data = await this.appRequest(`/app/installations/${installationId}/access_tokens`, "POST", jwt);
|
|
159
178
|
const token = {
|
|
@@ -199,10 +218,6 @@ var AppTokenSource = class {
|
|
|
199
218
|
});
|
|
200
219
|
return String(installations[0].id);
|
|
201
220
|
}
|
|
202
|
-
createAppJwt() {
|
|
203
|
-
if (!this.keyPromise) this.keyPromise = importPrivateKey(this.privateKey);
|
|
204
|
-
return this.keyPromise.then((key) => createJwt(key, this.appId));
|
|
205
|
-
}
|
|
206
221
|
async appRequest(path, method, jwt) {
|
|
207
222
|
const url = joinUrl(this.baseUrl, path);
|
|
208
223
|
const { fetchImpl } = this;
|
|
@@ -565,6 +580,17 @@ function github(options) {
|
|
|
565
580
|
};
|
|
566
581
|
}
|
|
567
582
|
if (params.namespace) {
|
|
583
|
+
if (tokenSource.kind === "app") {
|
|
584
|
+
const namespaceLower = params.namespace.toLowerCase();
|
|
585
|
+
const { data, response } = await http.json("/installation/repositories", {
|
|
586
|
+
query: { per_page: perPage },
|
|
587
|
+
signal: params.signal
|
|
588
|
+
});
|
|
589
|
+
return {
|
|
590
|
+
data: reposFromBody(data).filter((repo) => repo.owner.login.toLowerCase() === namespaceLower).map(mapRepository),
|
|
591
|
+
cursor: nextCursor(response)
|
|
592
|
+
};
|
|
593
|
+
}
|
|
568
594
|
try {
|
|
569
595
|
const { data, response } = await http.json(`/orgs/${encodeURIComponent(params.namespace)}/repos`, {
|
|
570
596
|
query: { per_page: perPage },
|
|
@@ -577,21 +603,18 @@ function github(options) {
|
|
|
577
603
|
} catch (error) {
|
|
578
604
|
if (!(error instanceof RepoError && error.code === "not_found")) throw error;
|
|
579
605
|
}
|
|
580
|
-
if (
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
cursor: nextCursor(response)
|
|
593
|
-
};
|
|
594
|
-
}
|
|
606
|
+
if ((await getLogin(params.signal)).toLowerCase() === params.namespace.toLowerCase()) {
|
|
607
|
+
const { data, response } = await http.json("/user/repos", {
|
|
608
|
+
query: {
|
|
609
|
+
affiliation: "owner",
|
|
610
|
+
per_page: perPage
|
|
611
|
+
},
|
|
612
|
+
signal: params.signal
|
|
613
|
+
});
|
|
614
|
+
return {
|
|
615
|
+
data: data.map(mapRepository),
|
|
616
|
+
cursor: nextCursor(response)
|
|
617
|
+
};
|
|
595
618
|
}
|
|
596
619
|
const { data, response } = await http.json(`/users/${encodeURIComponent(params.namespace)}/repos`, {
|
|
597
620
|
query: { per_page: perPage },
|
|
@@ -864,42 +887,47 @@ function github(options) {
|
|
|
864
887
|
}
|
|
865
888
|
//#endregion
|
|
866
889
|
//#region src/providers/github/installations.ts
|
|
890
|
+
function toAccount(payload) {
|
|
891
|
+
if (payload?.login === void 0) return void 0;
|
|
892
|
+
return {
|
|
893
|
+
id: String(payload.id),
|
|
894
|
+
login: payload.login,
|
|
895
|
+
kind: payload.type === "Organization" ? "organization" : "user",
|
|
896
|
+
avatarUrl: payload.avatar_url
|
|
897
|
+
};
|
|
898
|
+
}
|
|
867
899
|
function toInstallation(payload) {
|
|
868
|
-
const account = payload.account;
|
|
869
900
|
return {
|
|
870
901
|
id: String(payload.id),
|
|
871
|
-
account: account
|
|
872
|
-
id: String(account.id),
|
|
873
|
-
login: account.login,
|
|
874
|
-
kind: account.type === "Organization" ? "organization" : "user",
|
|
875
|
-
avatarUrl: account.avatar_url
|
|
876
|
-
},
|
|
902
|
+
account: toAccount(payload.account),
|
|
877
903
|
raw: payload
|
|
878
904
|
};
|
|
879
905
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
906
|
+
function toInstallationRequest(payload) {
|
|
907
|
+
return {
|
|
908
|
+
id: String(payload.id),
|
|
909
|
+
account: toAccount(payload.account),
|
|
910
|
+
createdAt: payload.created_at,
|
|
911
|
+
requester: toAccount(payload.requester),
|
|
912
|
+
raw: payload
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
async function listPage(bearer, defaultPath, params, toItems) {
|
|
888
916
|
const baseUrl = params.baseUrl ?? "https://api.github.com";
|
|
889
917
|
const http = new HttpClient({
|
|
890
918
|
provider: "github",
|
|
891
919
|
baseUrl,
|
|
892
920
|
fetchImpl: params.fetch,
|
|
893
921
|
authHeaders: () => ({
|
|
894
|
-
Authorization: `Bearer ${
|
|
922
|
+
Authorization: `Bearer ${bearer}`,
|
|
895
923
|
Accept: "application/vnd.github+json",
|
|
896
924
|
"X-GitHub-Api-Version": API_VERSION,
|
|
897
925
|
"User-Agent": USER_AGENT
|
|
898
926
|
}),
|
|
899
927
|
mapError,
|
|
900
|
-
secrets: () => [
|
|
928
|
+
secrets: () => [bearer]
|
|
901
929
|
});
|
|
902
|
-
let path =
|
|
930
|
+
let path = defaultPath;
|
|
903
931
|
let query;
|
|
904
932
|
if (params.cursor) {
|
|
905
933
|
const { url } = decodeCursor("github", params.cursor);
|
|
@@ -910,10 +938,32 @@ async function listUserInstallations(params) {
|
|
|
910
938
|
signal: params.signal
|
|
911
939
|
});
|
|
912
940
|
return {
|
|
913
|
-
data: (data
|
|
941
|
+
data: toItems(data),
|
|
914
942
|
cursor: nextCursor(response)
|
|
915
943
|
};
|
|
916
944
|
}
|
|
945
|
+
/**
|
|
946
|
+
* Lists the GitHub App installations the given user can access
|
|
947
|
+
* (`GET /user/installations`). Standalone by design: the endpoint requires a
|
|
948
|
+
* user access token, a different credential from the installation token the
|
|
949
|
+
* `github` provider authenticates its requests with.
|
|
950
|
+
*/
|
|
951
|
+
async function listUserInstallations(params) {
|
|
952
|
+
return listPage(params.token, "/user/installations", params, (payload) => (payload.installations ?? []).map(toInstallation));
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Lists the pending installation requests of a GitHub App
|
|
956
|
+
* (`GET /app/installation-requests`) — installations an organization owner has
|
|
957
|
+
* yet to approve. Standalone by design: the endpoint is authenticated with an
|
|
958
|
+
* app JWT, a different credential from the installation token the `github`
|
|
959
|
+
* provider authenticates its requests with.
|
|
960
|
+
*/
|
|
961
|
+
async function listInstallationRequests(params) {
|
|
962
|
+
return listPage(await createAppJwt({
|
|
963
|
+
appId: params.appId,
|
|
964
|
+
privateKey: params.privateKey
|
|
965
|
+
}), "/app/installation-requests", params, (payload) => payload.map(toInstallationRequest));
|
|
966
|
+
}
|
|
917
967
|
//#endregion
|
|
918
968
|
//#region src/providers/github/webhooks.ts
|
|
919
969
|
function verifyWebhook(params) {
|
|
@@ -949,4 +999,4 @@ async function parseWebhookEvent(input) {
|
|
|
949
999
|
};
|
|
950
1000
|
}
|
|
951
1001
|
//#endregion
|
|
952
|
-
export { commitWebUrl, github, listUserInstallations, parseWebhookEvent, verifyWebhook };
|
|
1002
|
+
export { commitWebUrl, github, listInstallationRequests, listUserInstallations, parseWebhookEvent, verifyWebhook };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "repo-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Unified TypeScript SDK for git providers — GitHub, GitLab, Bitbucket, Azure DevOps, Gitea",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Robin Genz <mail@robingenz.dev>",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"version-control",
|
|
23
23
|
"webhooks"
|
|
24
24
|
],
|
|
25
|
-
"homepage": "https://
|
|
25
|
+
"homepage": "https://repo-sdk.dev",
|
|
26
26
|
"repository": {
|
|
27
27
|
"type": "git",
|
|
28
28
|
"url": "https://github.com/capawesome-team/repo-sdk"
|