@bivy/bivy 0.18.5-staging.6 → 0.18.5-staging.8
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/dist/github-repos.js +57 -0
- package/dist/server.js +16 -36
- package/package.json +1 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** User credentials (configured token or gh login) can reach repos outside the Apps. */
|
|
2
|
+
export async function listGithubUserRepos(token, fetchImpl = fetch) {
|
|
3
|
+
const repos = [];
|
|
4
|
+
for (let page = 1;; page++) {
|
|
5
|
+
const response = await fetchImpl(`https://api.github.com/user/repos?sort=updated&per_page=100&affiliation=owner,collaborator,organization_member&page=${page}`, {
|
|
6
|
+
headers: { authorization: `Bearer ${token}`, accept: "application/vnd.github+json", "user-agent": "bivy" },
|
|
7
|
+
});
|
|
8
|
+
if (!response.ok)
|
|
9
|
+
throw new Error(`GitHub responded ${response.status} while listing user repositories`);
|
|
10
|
+
const batch = await response.json();
|
|
11
|
+
if (!Array.isArray(batch))
|
|
12
|
+
throw new Error("GitHub returned an invalid repository listing");
|
|
13
|
+
for (const repo of batch) {
|
|
14
|
+
if (typeof repo?.full_name !== "string" || !repo.full_name)
|
|
15
|
+
continue;
|
|
16
|
+
repos.push({
|
|
17
|
+
slug: repo.full_name,
|
|
18
|
+
description: typeof repo.description === "string" ? repo.description : "",
|
|
19
|
+
private: Boolean(repo.private),
|
|
20
|
+
pushedAt: typeof repo.pushed_at === "string" ? repo.pushed_at : undefined,
|
|
21
|
+
defaultBranch: typeof repo.default_branch === "string" ? repo.default_branch : undefined,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (batch.length < 100)
|
|
25
|
+
return repos;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Query both sources independently; a failed credential must not hide usable repos. */
|
|
29
|
+
export async function discoverGithubRepos(sources) {
|
|
30
|
+
const results = await Promise.allSettled([sources.appRepos(), sources.userRepos()]);
|
|
31
|
+
const repos = new Map();
|
|
32
|
+
const errors = [];
|
|
33
|
+
let authed = false;
|
|
34
|
+
for (const [index, result] of results.entries()) {
|
|
35
|
+
if (result.status === "rejected") {
|
|
36
|
+
errors.push(`${index === 0 ? "GitHub App" : "GitHub user"}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (result.value === undefined)
|
|
40
|
+
continue;
|
|
41
|
+
authed = true;
|
|
42
|
+
for (const repo of result.value) {
|
|
43
|
+
const key = repo.slug.toLowerCase();
|
|
44
|
+
if (!repos.has(key))
|
|
45
|
+
repos.set(key, repo);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const listing = {
|
|
49
|
+
authed,
|
|
50
|
+
repos: [...repos.values()].sort((a, b) => (b.pushedAt ?? "").localeCompare(a.pushedAt ?? "") || a.slug.localeCompare(b.slug)),
|
|
51
|
+
};
|
|
52
|
+
if (errors.length)
|
|
53
|
+
listing.error = errors.join("; ");
|
|
54
|
+
else if (!authed)
|
|
55
|
+
listing.reason = await sources.ghInstalled() ? "gh-unauthed" : "no-token";
|
|
56
|
+
return listing;
|
|
57
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -144,6 +144,7 @@ import { SecretVault, resolveSecret } from "./secrets.js";
|
|
|
144
144
|
import { deviceFlowClientId, requestDeviceCode, pollAccessTokenOnce, REPO_CONNECT_SCOPE } from "./github-device-auth.js";
|
|
145
145
|
import { InstallationTokenCache, createAppJwt, resolveInstallationId } from "./github-app-auth.js";
|
|
146
146
|
import { listGithubAppRepos } from "./github-app-repos.js";
|
|
147
|
+
import { discoverGithubRepos, listGithubUserRepos } from "./github-repos.js";
|
|
147
148
|
import { loadGitHubAppConfigs, orderAppsForOwner, listGitHubApps, removeGitHubApp, upsertGitHubApp, privateKeyIdFor, } from "./github-apps.js";
|
|
148
149
|
import { buildAppManifest, convertManifest, renderManifestForm } from "./github-app-manifest.js";
|
|
149
150
|
import { encryptGithubAppEnvelope, decryptGithubAppEnvelope, readLocalGithubAppVaultKey, writeLocalGithubAppVaultKey, forgetLocalGithubAppVaultKey, mintLocalGithubAppVaultKey, } from "./github-app-vault.js";
|
|
@@ -10492,46 +10493,25 @@ async function createControlPlaneHook(kind = "github") {
|
|
|
10492
10493
|
throw new Error(data?.error || `Could not create Bivy webhook (${res.status})`);
|
|
10493
10494
|
return data;
|
|
10494
10495
|
}
|
|
10495
|
-
//
|
|
10496
|
-
//
|
|
10497
|
-
// token stored on this machine (env or `gh`). Returns `authed: false` with
|
|
10498
|
-
// an empty list when there's no token so the UI falls back to manual owner/repo.
|
|
10496
|
+
// Combine local App access with the configured user token or gh login, matching
|
|
10497
|
+
// the credentials available to clone/fetch. Cache only complete, successful lists.
|
|
10499
10498
|
async function listAccessibleRepos() {
|
|
10500
10499
|
if (reposCache && Date.now() - reposCache.at < REPO_LIST_TTL_MS)
|
|
10501
10500
|
return reposCache.val;
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
|
|
10505
|
-
|
|
10506
|
-
|
|
10507
|
-
|
|
10508
|
-
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
});
|
|
10515
|
-
if (!ghRes.ok)
|
|
10516
|
-
return { authed: true, repos: [], error: `GitHub responded ${ghRes.status}` };
|
|
10517
|
-
const raw = (await ghRes.json().catch(() => []));
|
|
10518
|
-
const repos = (Array.isArray(raw) ? raw : []).map((r) => ({
|
|
10519
|
-
slug: String(r.full_name ?? ""),
|
|
10520
|
-
description: typeof r.description === "string" ? r.description : "",
|
|
10521
|
-
private: Boolean(r.private),
|
|
10522
|
-
pushedAt: typeof r.pushed_at === "string" ? r.pushed_at : undefined,
|
|
10523
|
-
// GitHub already hands us each repo's default branch here, so the branch
|
|
10524
|
-
// picker can label "Default branch (main)" instantly from the cached repo
|
|
10525
|
-
// list — no separate /repos/{owner}/{repo} round trip when it opens.
|
|
10526
|
-
defaultBranch: typeof r.default_branch === "string" ? r.default_branch : undefined,
|
|
10527
|
-
})).filter((r) => r.slug);
|
|
10528
|
-
const val = { authed: true, repos };
|
|
10501
|
+
const val = await discoverGithubRepos({
|
|
10502
|
+
appRepos: async () => {
|
|
10503
|
+
const apps = await ensureGitHubApps();
|
|
10504
|
+
return apps.length ? listGithubAppRepos(apps) : undefined;
|
|
10505
|
+
},
|
|
10506
|
+
userRepos: async () => {
|
|
10507
|
+
const token = await resolveGitHubToken();
|
|
10508
|
+
return token ? listGithubUserRepos(token) : undefined;
|
|
10509
|
+
},
|
|
10510
|
+
ghInstalled: ghCliInstalled,
|
|
10511
|
+
});
|
|
10512
|
+
if (val.authed && !val.error)
|
|
10529
10513
|
reposCache = { at: Date.now(), val };
|
|
10530
|
-
|
|
10531
|
-
}
|
|
10532
|
-
catch (error) {
|
|
10533
|
-
return { authed: false, repos: [], error: error instanceof Error ? error.message : String(error) };
|
|
10534
|
-
}
|
|
10514
|
+
return val;
|
|
10535
10515
|
}
|
|
10536
10516
|
let pendingGithubConnect = null;
|
|
10537
10517
|
async function startGithubConnect() {
|
package/package.json
CHANGED