repo-sdk 0.2.2 → 0.3.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 CHANGED
@@ -98,16 +98,16 @@ export async function POST(request: Request): Promise<Response> {
98
98
 
99
99
  ## API at a glance
100
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` |
101
+ | Namespace | Methods |
102
+ | ------------ | -------------------------------------------------------------------------------------- |
103
+ | `users` | `me` |
104
+ | `namespaces` | `list` · `listAll` |
105
+ | `repos` | `list` · `listAll` · `get` · `downloadArchive` · `getCloneCredentials` · `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
111
 
112
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
113
 
@@ -162,7 +162,7 @@ try {
162
162
  }
163
163
  ```
164
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.
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 deliberate exceptions are `repos.getCloneUrl`, which returns a URL with the credential embedded because that is what `git clone` needs, and `repos.getCloneCredentials`, which returns that credential next to a credential-free URL for a git credential helper — treat both as secrets.
166
166
 
167
167
  ## Runtime support
168
168
 
@@ -1,4 +1,4 @@
1
- import { B as TokenProvider, F as RepoProvider, O as ParsedWebhookEvent, v as IncomingWebhookRequest } from "./types-B9oMHTeP.js";
1
+ import { A as ParsedWebhookEvent, H as TokenProvider, L as RepoProvider, b as IncomingWebhookRequest } from "./types-E47xhSNA.js";
2
2
  import { t as VerifyWebhookParams$1 } from "./verify-p6JsExms.js";
3
3
  //#region src/providers/azure-devops/auth.d.ts
4
4
  /**
@@ -1,6 +1,6 @@
1
1
  import { t as RepoError } from "./errors-Cxq_CqYv.js";
2
- import { n as decodeCursor, r as encodeCursor } from "./pagination-CWWHSu8I.js";
3
- import { a as filenameFromContentDisposition, o as isRecord, r as commitWebUrlBuilder, t as HttpClient } from "./http-C3hAQeY0.js";
2
+ import { a as isRecord, i as filenameFromContentDisposition, l as decodeCursor, n as commitWebUrlBuilder, s as toCloneUrl, u as encodeCursor } from "./shared-C6m-C4My.js";
3
+ import { t as HttpClient } from "./http-BcC1vIan.js";
4
4
  import { a as headShaOrUndefined, n as timingSafeEqual, r as toIncomingWebhook } from "./verify-BBMVxz8q.js";
5
5
  //#region src/providers/azure-devops/auth.ts
6
6
  /**
@@ -215,6 +215,25 @@ function azureDevOps(options) {
215
215
  });
216
216
  return ref;
217
217
  }
218
+ async function getCloneCredentials(params) {
219
+ const { project, repository } = splitRepo(params.repo);
220
+ const url = `https://${new URL(baseUrl).host}/${enc(options.organization)}/${enc(project)}/_git/${enc(repository)}`;
221
+ if ("pat" in auth) return {
222
+ password: auth.pat,
223
+ url,
224
+ username: "pat"
225
+ };
226
+ if ("accessToken" in auth) return {
227
+ password: auth.accessToken,
228
+ url,
229
+ username: "oauth2"
230
+ };
231
+ return {
232
+ password: await auth.tokenProvider({ forceRefresh: false }),
233
+ url,
234
+ username: "oauth2"
235
+ };
236
+ }
218
237
  return {
219
238
  name: "azure-devops",
220
239
  capabilities: CAPABILITIES,
@@ -393,14 +412,9 @@ function azureDevOps(options) {
393
412
  filename: filenameFromContentDisposition(response.headers.get("content-disposition"))
394
413
  };
395
414
  },
415
+ getCloneCredentials,
396
416
  async getCloneUrl(params) {
397
- const { project, repository } = splitRepo(params.repo);
398
- const host = new URL(baseUrl).host;
399
- const repoPath = `${enc(options.organization)}/${enc(project)}/_git/${enc(repository)}`;
400
- if ("pat" in auth) return { url: `https://pat:${encodeURIComponent(auth.pat)}@${host}/${repoPath}` };
401
- if ("accessToken" in auth) return { url: `https://oauth2:${encodeURIComponent(auth.accessToken)}@${host}/${repoPath}` };
402
- const token = await auth.tokenProvider({ forceRefresh: false });
403
- return { url: `https://oauth2:${encodeURIComponent(token)}@${host}/${repoPath}` };
417
+ return toCloneUrl(await getCloneCredentials(params));
404
418
  },
405
419
  async createWebhook(params) {
406
420
  if (!params.events.includes("push") || !params.events.includes("tag_push")) throw new RepoError("Azure DevOps delivers branch and tag pushes together via a single 'git.push' subscription and cannot filter to a subset; request both 'push' and 'tag_push'", {
@@ -1,4 +1,4 @@
1
- import { B as TokenProvider, F as RepoProvider, O as ParsedWebhookEvent, v as IncomingWebhookRequest } from "./types-B9oMHTeP.js";
1
+ import { A as ParsedWebhookEvent, H as TokenProvider, L as RepoProvider, b as IncomingWebhookRequest } from "./types-E47xhSNA.js";
2
2
  import { t as VerifyWebhookParams } from "./verify-p6JsExms.js";
3
3
  //#region src/providers/bitbucket/index.d.ts
4
4
  interface BitbucketProviderOptions {
package/dist/bitbucket.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { t as RepoError } from "./errors-Cxq_CqYv.js";
2
- import { n as decodeCursor, r as encodeCursor, t as assertSameOriginUrl } from "./pagination-CWWHSu8I.js";
3
- import { a as filenameFromContentDisposition, n as clampPerPage, o as isRecord, r as commitWebUrlBuilder, t as HttpClient } from "./http-C3hAQeY0.js";
2
+ import { a as isRecord, c as assertSameOriginUrl, i as filenameFromContentDisposition, l as decodeCursor, n as commitWebUrlBuilder, s as toCloneUrl, t as clampPerPage, u as encodeCursor } from "./shared-C6m-C4My.js";
3
+ import { t as HttpClient } from "./http-BcC1vIan.js";
4
4
  import { a as headShaOrUndefined, i as verifyHmacSignature, r as toIncomingWebhook } from "./verify-BBMVxz8q.js";
5
5
  //#region src/providers/bitbucket/index.ts
6
6
  const BASE_URL = "https://api.bitbucket.org/2.0";
@@ -167,6 +167,19 @@ function bitbucket(options) {
167
167
  function hookPath(repo, id) {
168
168
  return `${repoPath(repo)}/hooks/${encodeURIComponent(id)}`;
169
169
  }
170
+ async function getCloneCredentials(params) {
171
+ const url = `https://${GIT_HOST}/${params.repo}.git`;
172
+ if (isApiTokenAuth(auth)) return {
173
+ password: auth.apiToken,
174
+ url,
175
+ username: "x-bitbucket-api-token-auth"
176
+ };
177
+ return {
178
+ password: await currentToken(false),
179
+ url,
180
+ username: "x-token-auth"
181
+ };
182
+ }
170
183
  return {
171
184
  name: "bitbucket",
172
185
  capabilities: CAPABILITIES,
@@ -338,10 +351,9 @@ function bitbucket(options) {
338
351
  filename: filenameFromContentDisposition(response.headers.get("content-disposition"))
339
352
  };
340
353
  },
354
+ getCloneCredentials,
341
355
  async getCloneUrl(params) {
342
- if (isApiTokenAuth(auth)) return { url: `https://x-bitbucket-api-token-auth:${encodeURIComponent(auth.apiToken)}@${GIT_HOST}/${params.repo}.git` };
343
- const token = await currentToken(false);
344
- return { url: `https://x-token-auth:${encodeURIComponent(token)}@${GIT_HOST}/${params.repo}.git` };
356
+ return toCloneUrl(await getCloneCredentials(params));
345
357
  },
346
358
  async createWebhook(params) {
347
359
  const { data } = await http.json(`${repoPath(params.repo)}/hooks`, {
@@ -1,4 +1,4 @@
1
- import { B as TokenProvider, F as RepoProvider } from "./types-B9oMHTeP.js";
1
+ import { H as TokenProvider, L as RepoProvider } from "./types-E47xhSNA.js";
2
2
  //#region src/providers/git-http/index.d.ts
3
3
  interface GitHttpProviderOptions {
4
4
  /**
package/dist/git-http.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { t as RepoError } from "./errors-Cxq_CqYv.js";
2
- import { a as stringToBase64, n as decodeCursor, r as encodeCursor } from "./pagination-CWWHSu8I.js";
3
- import { n as clampPerPage, t as HttpClient } from "./http-C3hAQeY0.js";
2
+ import { f as stringToBase64, l as decodeCursor, s as toCloneUrl, t as clampPerPage, u as encodeCursor } from "./shared-C6m-C4My.js";
3
+ import { t as HttpClient } from "./http-BcC1vIan.js";
4
4
  //#region src/providers/git-http/index.ts
5
5
  const CAPABILITIES = {
6
6
  userProfile: false,
@@ -192,6 +192,19 @@ function gitHttp(options = {}) {
192
192
  }) : void 0
193
193
  };
194
194
  }
195
+ async function getCloneCredentials(params) {
196
+ const url = normalizeRepoUrl(params.repo);
197
+ if (!auth) return {
198
+ password: null,
199
+ url,
200
+ username: null
201
+ };
202
+ return {
203
+ password: await currentSecret(false),
204
+ url,
205
+ username
206
+ };
207
+ }
195
208
  return {
196
209
  name: "git-http",
197
210
  capabilities: CAPABILITIES,
@@ -288,13 +301,9 @@ function gitHttp(options = {}) {
288
301
  async downloadArchive() {
289
302
  throw unsupported("archive downloads");
290
303
  },
304
+ getCloneCredentials,
291
305
  async getCloneUrl(params) {
292
- const repoUrl = normalizeRepoUrl(params.repo);
293
- if (!auth) return { url: repoUrl };
294
- const url = new URL(repoUrl);
295
- url.username = username;
296
- url.password = await currentSecret(false);
297
- return { url: url.toString() };
306
+ return toCloneUrl(await getCloneCredentials(params));
298
307
  },
299
308
  async createWebhook() {
300
309
  throw unsupported("webhooks");
package/dist/gitea.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { B as TokenProvider, F as RepoProvider, O as ParsedWebhookEvent, v as IncomingWebhookRequest } from "./types-B9oMHTeP.js";
1
+ import { A as ParsedWebhookEvent, H as TokenProvider, L as RepoProvider, b as IncomingWebhookRequest } from "./types-E47xhSNA.js";
2
2
  import { t as VerifyWebhookParams } from "./verify-p6JsExms.js";
3
3
  //#region src/providers/gitea/index.d.ts
4
4
  interface GiteaProviderOptions {
package/dist/gitea.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { t as RepoError } from "./errors-Cxq_CqYv.js";
2
- import { n as decodeCursor, r as encodeCursor, t as assertSameOriginUrl } from "./pagination-CWWHSu8I.js";
3
- import { a as filenameFromContentDisposition, i as encodeRefPath, n as clampPerPage, o as isRecord, r as commitWebUrlBuilder, s as parseLinkNext, t as HttpClient } from "./http-C3hAQeY0.js";
2
+ import { a as isRecord, c as assertSameOriginUrl, i as filenameFromContentDisposition, l as decodeCursor, n as commitWebUrlBuilder, o as parseLinkNext, r as encodeRefPath, s as toCloneUrl, t as clampPerPage, u as encodeCursor } from "./shared-C6m-C4My.js";
3
+ import { t as HttpClient } from "./http-BcC1vIan.js";
4
4
  import { a as headShaOrUndefined, n as timingSafeEqual, r as toIncomingWebhook, t as hmacSha256Hex } from "./verify-BBMVxz8q.js";
5
5
  //#region src/providers/gitea/index.ts
6
6
  const DEFAULT_BASE_URL = "https://gitea.com/api/v1";
@@ -189,6 +189,13 @@ function gitea(options) {
189
189
  function repoPath(repo) {
190
190
  return `/repos/${repo}`;
191
191
  }
192
+ async function getCloneCredentials(params) {
193
+ return {
194
+ password: null,
195
+ url: `https://${new URL(baseUrl).host}/${params.repo}.git`,
196
+ username: await currentToken(false)
197
+ };
198
+ }
192
199
  return {
193
200
  name: "gitea",
194
201
  capabilities: CAPABILITIES,
@@ -400,10 +407,9 @@ function gitea(options) {
400
407
  filename: filenameFromContentDisposition(response.headers.get("content-disposition"))
401
408
  };
402
409
  },
410
+ getCloneCredentials,
403
411
  async getCloneUrl(params) {
404
- const host = new URL(baseUrl).host;
405
- const token = await currentToken(false);
406
- return { url: `https://${encodeURIComponent(token)}@${host}/${params.repo}.git` };
412
+ return toCloneUrl(await getCloneCredentials(params));
407
413
  },
408
414
  async createWebhook(params) {
409
415
  const { data } = await http.json(`${repoPath(params.repo)}/hooks`, {
package/dist/github.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { B as TokenProvider, D as Page, F as RepoProvider, O as ParsedWebhookEvent, v as IncomingWebhookRequest } from "./types-B9oMHTeP.js";
1
+ import { A as ParsedWebhookEvent, H as TokenProvider, L as RepoProvider, b as IncomingWebhookRequest, k as Page } from "./types-E47xhSNA.js";
2
2
  import { t as VerifyWebhookParams } from "./verify-p6JsExms.js";
3
3
  //#region src/providers/github/app-auth.d.ts
4
4
  interface InstallationToken {
package/dist/github.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { n as codeFromStatus, t as RepoError } from "./errors-Cxq_CqYv.js";
2
- import { i as bytesToBase64Url, n as decodeCursor, r as encodeCursor, t as assertSameOriginUrl } from "./pagination-CWWHSu8I.js";
3
- import { a as filenameFromContentDisposition, i as encodeRefPath, n as clampPerPage, o as isRecord, r as commitWebUrlBuilder, s as parseLinkNext, t as HttpClient } from "./http-C3hAQeY0.js";
2
+ import { a as isRecord, c as assertSameOriginUrl, d as bytesToBase64Url, i as filenameFromContentDisposition, l as decodeCursor, n as commitWebUrlBuilder, o as parseLinkNext, r as encodeRefPath, s as toCloneUrl, t as clampPerPage, u as encodeCursor } from "./shared-C6m-C4My.js";
3
+ import { t as HttpClient } from "./http-BcC1vIan.js";
4
4
  import { a as headShaOrUndefined, i as verifyHmacSignature, r as toIncomingWebhook } from "./verify-BBMVxz8q.js";
5
5
  //#region src/providers/github/app-auth.ts
6
6
  const RSA_ALGORITHM_IDENTIFIER = [
@@ -501,6 +501,15 @@ function github(options) {
501
501
  raw: owner
502
502
  }] };
503
503
  }
504
+ function getCloneCredentials(params) {
505
+ const host = gitHostFromBaseUrl(baseUrl);
506
+ return tokenSource.getTokenWithExpiry().then(({ token, expiresAt }) => ({
507
+ expiresAt,
508
+ password: token,
509
+ url: `https://${host}/${params.repo}.git`,
510
+ username: "x-access-token"
511
+ }));
512
+ }
504
513
  return {
505
514
  name: "github",
506
515
  capabilities: {
@@ -805,12 +814,9 @@ function github(options) {
805
814
  filename: filenameFromContentDisposition(archiveResponse.headers.get("content-disposition"))
806
815
  };
807
816
  },
808
- getCloneUrl(params) {
809
- const host = gitHostFromBaseUrl(baseUrl);
810
- return tokenSource.getTokenWithExpiry().then(({ token, expiresAt }) => ({
811
- url: `https://x-access-token:${token}@${host}/${params.repo}.git`,
812
- expiresAt
813
- }));
817
+ getCloneCredentials,
818
+ async getCloneUrl(params) {
819
+ return toCloneUrl(await getCloneCredentials(params));
814
820
  },
815
821
  async createWebhook(params) {
816
822
  const { data } = await http.json(`${repoPath(params.repo)}/hooks`, {
package/dist/gitlab.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { B as TokenProvider, F as RepoProvider, O as ParsedWebhookEvent, v as IncomingWebhookRequest } from "./types-B9oMHTeP.js";
1
+ import { A as ParsedWebhookEvent, H as TokenProvider, L as RepoProvider, b as IncomingWebhookRequest } from "./types-E47xhSNA.js";
2
2
  import { t as VerifyWebhookParams } from "./verify-p6JsExms.js";
3
3
  //#region src/providers/gitlab/index.d.ts
4
4
  interface GitLabProviderOptions {
package/dist/gitlab.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { t as RepoError } from "./errors-Cxq_CqYv.js";
2
- import { n as decodeCursor, r as encodeCursor, t as assertSameOriginUrl } from "./pagination-CWWHSu8I.js";
3
- import { a as filenameFromContentDisposition, n as clampPerPage, o as isRecord, r as commitWebUrlBuilder, s as parseLinkNext, t as HttpClient } from "./http-C3hAQeY0.js";
2
+ import { a as isRecord, c as assertSameOriginUrl, i as filenameFromContentDisposition, l as decodeCursor, n as commitWebUrlBuilder, o as parseLinkNext, s as toCloneUrl, t as clampPerPage, u as encodeCursor } from "./shared-C6m-C4My.js";
3
+ import { t as HttpClient } from "./http-BcC1vIan.js";
4
4
  import { a as headShaOrUndefined, n as timingSafeEqual, r as toIncomingWebhook } from "./verify-BBMVxz8q.js";
5
5
  //#region src/providers/gitlab/index.ts
6
6
  const DEFAULT_BASE_URL = "https://gitlab.com/api/v4";
@@ -161,9 +161,6 @@ function assertActiveSupported(active) {
161
161
  provider: "gitlab"
162
162
  });
163
163
  }
164
- function injectCredentials(cloneUrl, token) {
165
- return cloneUrl.replace("://", `://oauth2:${encodeURIComponent(token)}@`);
166
- }
167
164
  function gitHostFromBaseUrl(baseUrl) {
168
165
  return new URL(baseUrl).host;
169
166
  }
@@ -205,6 +202,22 @@ function gitlab(options) {
205
202
  function hooksPath(repo) {
206
203
  return `/projects/${projectId(repo)}/hooks`;
207
204
  }
205
+ async function getCloneCredentials(params) {
206
+ const password = await tokenSource.getToken();
207
+ if (isNumericId(params.repo)) {
208
+ const { data } = await http.json(`/projects/${params.repo}`, { signal: params.signal });
209
+ return {
210
+ password,
211
+ url: data.http_url_to_repo,
212
+ username: "oauth2"
213
+ };
214
+ }
215
+ return {
216
+ password,
217
+ url: `https://${gitHostFromBaseUrl(baseUrl)}/${params.repo}.git`,
218
+ username: "oauth2"
219
+ };
220
+ }
208
221
  return {
209
222
  name: "gitlab",
210
223
  capabilities: CAPABILITIES,
@@ -433,14 +446,9 @@ function gitlab(options) {
433
446
  filename: filenameFromContentDisposition(response.headers.get("content-disposition"))
434
447
  };
435
448
  },
449
+ getCloneCredentials,
436
450
  async getCloneUrl(params) {
437
- const token = await tokenSource.getToken();
438
- if (isNumericId(params.repo)) {
439
- const { data } = await http.json(`/projects/${params.repo}`, { signal: params.signal });
440
- return { url: injectCredentials(data.http_url_to_repo, token) };
441
- }
442
- const host = gitHostFromBaseUrl(baseUrl);
443
- return { url: `https://oauth2:${encodeURIComponent(token)}@${host}/${params.repo}.git` };
451
+ return toCloneUrl(await getCloneCredentials(params));
444
452
  },
445
453
  async createWebhook(params) {
446
454
  assertActiveSupported(params.active);
@@ -1,35 +1,4 @@
1
1
  import { n as codeFromStatus, r as parseRetryAfter, t as RepoError } from "./errors-Cxq_CqYv.js";
2
- //#region src/providers/shared.ts
3
- function isRecord(value) {
4
- return typeof value === "object" && value !== null;
5
- }
6
- function clampPerPage(limit, max = 100) {
7
- if (limit === void 0) return void 0;
8
- return Math.min(Math.max(1, Math.trunc(limit)), max);
9
- }
10
- function parseLinkNext(header) {
11
- if (!header) return void 0;
12
- for (const part of header.split(",")) {
13
- const match = part.match(/<([^>]+)>\s*;\s*rel="([^"]+)"/);
14
- if (match && match[2] === "next") return match[1];
15
- }
16
- }
17
- /** Percent-encodes a ref path while keeping its `/` separators intact. */
18
- function encodeRefPath(ref) {
19
- return ref.split("/").map(encodeURIComponent).join("/");
20
- }
21
- function commitWebUrlBuilder(pathSegment) {
22
- return function commitWebUrl(repoWebUrl, sha) {
23
- return `${repoWebUrl.replace(/\/+$/, "")}/${pathSegment}/${sha}`;
24
- };
25
- }
26
- function filenameFromContentDisposition(header) {
27
- if (!header) return void 0;
28
- const extended = header.match(/filename\*=(?:UTF-8'')?([^;]+)/i);
29
- if (extended?.[1]) return decodeURIComponent(extended[1].trim().replace(/^"|"$/g, ""));
30
- return header.match(/filename="?([^";]+)"?/i)?.[1]?.trim();
31
- }
32
- //#endregion
33
2
  //#region src/http.ts
34
3
  const USER_AGENT = "repo-sdk";
35
4
  var HttpClient = class {
@@ -104,4 +73,4 @@ var HttpClient = class {
104
73
  }
105
74
  };
106
75
  //#endregion
107
- export { filenameFromContentDisposition as a, encodeRefPath as i, clampPerPage as n, isRecord as o, commitWebUrlBuilder as r, parseLinkNext as s, HttpClient as t };
76
+ export { HttpClient as t };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as ProviderRefMatch, B as TokenProvider, C as ListTagsParams, D as Page, E as NamespaceKind, F as RepoProvider, G as WebhookVerificationMethod, H as UserRef, I as Repository, L as ResolveRefParams, M as RefMatch, N as RefType, O as ParsedWebhookEvent, P as RepoCapabilities, R as SearchRefsParams, S as ListRepositoriesParams, T as Namespace, U as Webhook, V as UpdateWebhookParams, W as WebhookEventType, _ as GitActor, a as CloneUrl, b as ListCommitsParams, c as DeleteWebhookParams, d as GetBranchParams, f as GetCloneUrlParams, g as GetWebhookParams, h as GetTagParams, i as Branch, j as ProviderSearchRefsParams, k as ProviderName, l as DownloadArchiveParams, m as GetRepositoryParams, n as ArchiveFormat, o as Commit, p as GetCommitParams, r as AuthenticatedUser, s as CreateWebhookParams, t as Archive, u as GetAuthenticatedUserParams, v as IncomingWebhookRequest, w as ListWebhooksParams, x as ListNamespacesParams, y as ListBranchesParams, z as Tag } from "./types-B9oMHTeP.js";
1
+ import { A as ParsedWebhookEvent, B as SearchRefsParams, C as ListNamespacesParams, D as Namespace, E as ListWebhooksParams, F as RefType, G as Webhook, H as TokenProvider, I as RepoCapabilities, K as WebhookEventType, L as RepoProvider, M as ProviderRefMatch, N as ProviderSearchRefsParams, O as NamespaceKind, P as RefMatch, R as Repository, S as ListCommitsParams, T as ListTagsParams, U as UpdateWebhookParams, V as Tag, W as UserRef, _ as GetTagParams, a as CloneCredentials, b as IncomingWebhookRequest, c as CreateWebhookParams, d as GetAuthenticatedUserParams, f as GetBranchParams, g as GetRepositoryParams, h as GetCommitParams, i as Branch, j as ProviderName, k as Page, l as DeleteWebhookParams, m as GetCloneUrlParams, n as ArchiveFormat, o as CloneUrl, p as GetCloneCredentialsParams, q as WebhookVerificationMethod, r as AuthenticatedUser, s as Commit, t as Archive, u as DownloadArchiveParams, v as GetWebhookParams, w as ListRepositoriesParams, x as ListBranchesParams, y as GitActor, z as ResolveRefParams } from "./types-E47xhSNA.js";
2
2
  //#region src/client.d.ts
3
3
  interface RetryOptions {
4
4
  rateLimit?: boolean;
@@ -23,6 +23,7 @@ interface RepoClient {
23
23
  listAll(params?: Omit<ListRepositoriesParams, 'cursor'>): AsyncGenerator<Repository, void>;
24
24
  get(params: GetRepositoryParams): Promise<Repository>;
25
25
  downloadArchive(params: DownloadArchiveParams): Promise<Archive>;
26
+ getCloneCredentials(params: GetCloneCredentialsParams): Promise<CloneCredentials>;
26
27
  getCloneUrl(params: GetCloneUrlParams): Promise<CloneUrl>;
27
28
  };
28
29
  commits: {
@@ -92,4 +93,4 @@ declare class RepoError extends Error {
92
93
  constructor(message: string, options: RepoErrorOptions);
93
94
  }
94
95
  //#endregion
95
- export { type Archive, type ArchiveFormat, type AuthenticatedUser, type Branch, type CloneUrl, type Commit, type CreateClientOptions, type CreateWebhookParams, type DeleteWebhookParams, type DetectWebhookProviderInput, type DownloadArchiveParams, type GetAuthenticatedUserParams, type GetBranchParams, type GetCloneUrlParams, type GetCommitParams, type GetRepositoryParams, type GetTagParams, type GetWebhookParams, type GitActor, type IncomingWebhookRequest, type ListBranchesParams, type ListCommitsParams, type ListNamespacesParams, type ListRepositoriesParams, type ListTagsParams, type ListWebhooksParams, type Namespace, type NamespaceKind, type Page, type ParsedWebhookEvent, type ProviderName, type ProviderRefMatch, type ProviderSearchRefsParams, type RefMatch, type RefType, type RepoCapabilities, type RepoClient, RepoError, type RepoErrorCode, type RepoErrorOptions, type RepoProvider, type Repository, type ResolveRefParams, type RetryOptions, type SearchRefsParams, type Tag, type TokenProvider, type UpdateWebhookParams, type UserRef, type Webhook, type WebhookEventType, type WebhookVerificationMethod, createClient, detectWebhookProvider };
96
+ export { type Archive, type ArchiveFormat, type AuthenticatedUser, type Branch, type CloneCredentials, type CloneUrl, type Commit, type CreateClientOptions, type CreateWebhookParams, type DeleteWebhookParams, type DetectWebhookProviderInput, type DownloadArchiveParams, type GetAuthenticatedUserParams, type GetBranchParams, type GetCloneCredentialsParams, type GetCloneUrlParams, type GetCommitParams, type GetRepositoryParams, type GetTagParams, type GetWebhookParams, type GitActor, type IncomingWebhookRequest, type ListBranchesParams, type ListCommitsParams, type ListNamespacesParams, type ListRepositoriesParams, type ListTagsParams, type ListWebhooksParams, type Namespace, type NamespaceKind, type Page, type ParsedWebhookEvent, type ProviderName, type ProviderRefMatch, type ProviderSearchRefsParams, type RefMatch, type RefType, type RepoCapabilities, type RepoClient, RepoError, type RepoErrorCode, type RepoErrorOptions, type RepoProvider, type Repository, type ResolveRefParams, type RetryOptions, type SearchRefsParams, type Tag, type TokenProvider, type UpdateWebhookParams, type UserRef, type Webhook, type WebhookEventType, type WebhookVerificationMethod, createClient, detectWebhookProvider };
package/dist/index.js CHANGED
@@ -125,6 +125,10 @@ function createClient(options) {
125
125
  format
126
126
  }));
127
127
  },
128
+ getCloneCredentials: async (params) => {
129
+ requireNonEmpty(params.repo, "repo");
130
+ return withRetry(() => provider.getCloneCredentials(params));
131
+ },
128
132
  getCloneUrl: async (params) => {
129
133
  requireNonEmpty(params.repo, "repo");
130
134
  return withRetry(() => provider.getCloneUrl(params));
@@ -70,4 +70,54 @@ function assertSameOriginUrl(provider, baseUrl, url) {
70
70
  return url;
71
71
  }
72
72
  //#endregion
73
- export { stringToBase64 as a, bytesToBase64Url as i, decodeCursor as n, encodeCursor as r, assertSameOriginUrl as t };
73
+ //#region src/providers/shared.ts
74
+ function isRecord(value) {
75
+ return typeof value === "object" && value !== null;
76
+ }
77
+ function clampPerPage(limit, max = 100) {
78
+ if (limit === void 0) return void 0;
79
+ return Math.min(Math.max(1, Math.trunc(limit)), max);
80
+ }
81
+ function parseLinkNext(header) {
82
+ if (!header) return void 0;
83
+ for (const part of header.split(",")) {
84
+ const match = part.match(/<([^>]+)>\s*;\s*rel="([^"]+)"/);
85
+ if (match && match[2] === "next") return match[1];
86
+ }
87
+ }
88
+ /** Percent-encodes a ref path while keeping its `/` separators intact. */
89
+ function encodeRefPath(ref) {
90
+ return ref.split("/").map(encodeURIComponent).join("/");
91
+ }
92
+ function commitWebUrlBuilder(pathSegment) {
93
+ return function commitWebUrl(repoWebUrl, sha) {
94
+ return `${repoWebUrl.replace(/\/+$/, "")}/${pathSegment}/${sha}`;
95
+ };
96
+ }
97
+ function filenameFromContentDisposition(header) {
98
+ if (!header) return void 0;
99
+ const extended = header.match(/filename\*=(?:UTF-8'')?([^;]+)/i);
100
+ if (extended?.[1]) return decodeURIComponent(extended[1].trim().replace(/^"|"$/g, ""));
101
+ return header.match(/filename="?([^";]+)"?/i)?.[1]?.trim();
102
+ }
103
+ /**
104
+ * Embeds clone credentials into their credential-free URL as percent-encoded
105
+ * userinfo — the single place every provider's tokenized clone URL is built.
106
+ * A `null` password yields a bare `user@host` (Gitea puts the token in the
107
+ * username slot); anonymous credentials return the URL untouched.
108
+ */
109
+ function toCloneUrl(credentials) {
110
+ const { expiresAt, password, url, username } = credentials;
111
+ if (username === null && password === null) return {
112
+ url,
113
+ expiresAt
114
+ };
115
+ const user = username === null ? "" : encodeURIComponent(username);
116
+ const userinfo = password === null ? user : `${user}:${encodeURIComponent(password)}`;
117
+ return {
118
+ url: url.replace("://", `://${userinfo}@`),
119
+ expiresAt
120
+ };
121
+ }
122
+ //#endregion
123
+ export { isRecord as a, assertSameOriginUrl as c, bytesToBase64Url as d, stringToBase64 as f, filenameFromContentDisposition as i, decodeCursor as l, commitWebUrlBuilder as n, parseLinkNext as o, encodeRefPath as r, toCloneUrl as s, clampPerPage as t, encodeCursor as u };
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { E as NamespaceKind, F as RepoProvider, I as Repository, P as RepoCapabilities, T as Namespace, U as Webhook, W as WebhookEventType, i as Branch, k as ProviderName, o as Commit, z as Tag } from "./types-B9oMHTeP.js";
1
+ import { D as Namespace, G as Webhook, I as RepoCapabilities, K as WebhookEventType, L as RepoProvider, O as NamespaceKind, R as Repository, V as Tag, i as Branch, j as ProviderName, s as Commit } from "./types-E47xhSNA.js";
2
2
  //#region src/providers/testing/index.d.ts
3
3
  interface InMemoryNamespaceSeed {
4
4
  id: string;
package/dist/testing.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as RepoError } from "./errors-Cxq_CqYv.js";
2
- import { n as decodeCursor, r as encodeCursor } from "./pagination-CWWHSu8I.js";
2
+ import { l as decodeCursor, s as toCloneUrl, u as encodeCursor } from "./shared-C6m-C4My.js";
3
3
  //#region src/providers/testing/index.ts
4
4
  /**
5
5
  * Small on purpose so that consumers exercise cursor-based pagination.
@@ -149,6 +149,14 @@ function createInMemoryProvider(seed = {}, options = {}) {
149
149
  if (!repository) throw notFound(`Unknown repository: ${repo}`);
150
150
  return repository;
151
151
  }
152
+ function getCloneCredentials(params) {
153
+ requireRepository(params.repo);
154
+ return Promise.resolve({
155
+ password: "test",
156
+ url: `https://in-memory.invalid/${params.repo}.git`,
157
+ username: "x-token"
158
+ });
159
+ }
152
160
  return {
153
161
  name,
154
162
  capabilities,
@@ -262,9 +270,9 @@ function createInMemoryProvider(seed = {}, options = {}) {
262
270
  filename: `${repository.name}-${params.ref}.${extension}`
263
271
  });
264
272
  },
265
- getCloneUrl(params) {
266
- requireRepository(params.repo);
267
- return Promise.resolve({ url: `https://x-token:test@in-memory.invalid/${params.repo}.git` });
273
+ getCloneCredentials,
274
+ async getCloneUrl(params) {
275
+ return toCloneUrl(await getCloneCredentials(params));
268
276
  },
269
277
  createWebhook(params) {
270
278
  requireRepository(params.repo);
@@ -45,6 +45,20 @@ interface CloneUrl {
45
45
  url: string;
46
46
  expiresAt?: Date;
47
47
  }
48
+ /**
49
+ * Clone credentials kept separate from the URL, for handing to a git credential
50
+ * helper instead of persisting a tokenized remote in `.git/config`. Both
51
+ * components are raw, never percent-encoded.
52
+ */
53
+ interface CloneCredentials {
54
+ expiresAt?: Date;
55
+ /** The secret, or `null` when the provider carries the token in `username` (Gitea) or access is anonymous. */
56
+ password: string | null;
57
+ /** Clone URL without any embedded credential. */
58
+ url: string;
59
+ /** The static username the provider expects, the token itself for Gitea, or `null` for anonymous access. */
60
+ username: string | null;
61
+ }
48
62
  /** The account behind the configured credentials (see the `userProfile` capability). */
49
63
  interface AuthenticatedUser {
50
64
  id: string;
@@ -215,6 +229,9 @@ interface DownloadArchiveParams extends BaseParams {
215
229
  ref: string;
216
230
  format?: ArchiveFormat;
217
231
  }
232
+ interface GetCloneCredentialsParams extends BaseParams {
233
+ repo: string;
234
+ }
218
235
  interface GetCloneUrlParams extends BaseParams {
219
236
  repo: string;
220
237
  }
@@ -276,6 +293,8 @@ interface RepoProvider {
276
293
  /** Prefix-match refs of the requested types, branches before tags, at most `limit` results. */
277
294
  searchRefs(params: ProviderSearchRefsParams): Promise<ProviderRefMatch[]>;
278
295
  downloadArchive(params: DownloadArchiveParams): Promise<Archive>;
296
+ getCloneCredentials(params: GetCloneCredentialsParams): Promise<CloneCredentials>;
297
+ /** The same credentials embedded in the URL; derived from `getCloneCredentials`. */
279
298
  getCloneUrl(params: GetCloneUrlParams): Promise<CloneUrl>;
280
299
  createWebhook(params: CreateWebhookParams): Promise<Webhook>;
281
300
  listWebhooks(params: ListWebhooksParams): Promise<Page<Webhook>>;
@@ -284,4 +303,4 @@ interface RepoProvider {
284
303
  deleteWebhook(params: DeleteWebhookParams): Promise<void>;
285
304
  }
286
305
  //#endregion
287
- export { ProviderRefMatch as A, TokenProvider as B, ListTagsParams as C, Page as D, NamespaceKind as E, RepoProvider as F, WebhookVerificationMethod as G, UserRef as H, Repository as I, ResolveRefParams as L, RefMatch as M, RefType as N, ParsedWebhookEvent as O, RepoCapabilities as P, SearchRefsParams as R, ListRepositoriesParams as S, Namespace as T, Webhook as U, UpdateWebhookParams as V, WebhookEventType as W, GitActor as _, CloneUrl as a, ListCommitsParams as b, DeleteWebhookParams as c, GetBranchParams as d, GetCloneUrlParams as f, GetWebhookParams as g, GetTagParams as h, Branch as i, ProviderSearchRefsParams as j, ProviderName as k, DownloadArchiveParams as l, GetRepositoryParams as m, ArchiveFormat as n, Commit as o, GetCommitParams as p, AuthenticatedUser as r, CreateWebhookParams as s, Archive as t, GetAuthenticatedUserParams as u, IncomingWebhookRequest as v, ListWebhooksParams as w, ListNamespacesParams as x, ListBranchesParams as y, Tag as z };
306
+ export { ParsedWebhookEvent as A, SearchRefsParams as B, ListNamespacesParams as C, Namespace as D, ListWebhooksParams as E, RefType as F, Webhook as G, TokenProvider as H, RepoCapabilities as I, WebhookEventType as K, RepoProvider as L, ProviderRefMatch as M, ProviderSearchRefsParams as N, NamespaceKind as O, RefMatch as P, Repository as R, ListCommitsParams as S, ListTagsParams as T, UpdateWebhookParams as U, Tag as V, UserRef as W, GetTagParams as _, CloneCredentials as a, IncomingWebhookRequest as b, CreateWebhookParams as c, GetAuthenticatedUserParams as d, GetBranchParams as f, GetRepositoryParams as g, GetCommitParams as h, Branch as i, ProviderName as j, Page as k, DeleteWebhookParams as l, GetCloneUrlParams as m, ArchiveFormat as n, CloneUrl as o, GetCloneCredentialsParams as p, WebhookVerificationMethod as q, AuthenticatedUser as r, Commit as s, Archive as t, DownloadArchiveParams as u, GetWebhookParams as v, ListRepositoriesParams as w, ListBranchesParams as x, GitActor as y, ResolveRefParams as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repo-sdk",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
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>",