@h-rig/github-provider-plugin 0.0.6-alpha.156
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 +1 -0
- package/dist/src/auth-store.d.ts +42 -0
- package/dist/src/auth-store.js +226 -0
- package/dist/src/credentials.d.ts +20 -0
- package/dist/src/credentials.js +118 -0
- package/dist/src/github-api.d.ts +107 -0
- package/dist/src/github-api.js +451 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.js +1247 -0
- package/dist/src/issue-analysis.d.ts +129 -0
- package/dist/src/issue-analysis.js +382 -0
- package/dist/src/plugin.d.ts +4 -0
- package/dist/src/plugin.js +94 -0
- package/dist/src/projects.d.ts +31 -0
- package/dist/src/projects.js +147 -0
- package/dist/src/service.d.ts +7 -0
- package/dist/src/service.js +43 -0
- package/dist/src/triage-run.d.ts +25 -0
- package/dist/src/triage-run.js +474 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# @h-rig/github-provider-plugin
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { GitHubAuthStatus } from "@rig/contracts";
|
|
2
|
+
export type { GitHubAuthStatus };
|
|
3
|
+
type PendingDeviceFlow = {
|
|
4
|
+
readonly pollId: string;
|
|
5
|
+
readonly deviceCode: string;
|
|
6
|
+
readonly expiresAt: string;
|
|
7
|
+
readonly intervalSeconds: number;
|
|
8
|
+
};
|
|
9
|
+
export type GitHubAuthStore = {
|
|
10
|
+
readonly stateFile: string;
|
|
11
|
+
readonly status: (options?: {
|
|
12
|
+
oauthConfigured?: boolean;
|
|
13
|
+
}) => GitHubAuthStatus;
|
|
14
|
+
readonly readToken: () => string | null;
|
|
15
|
+
readonly saveToken: (input: {
|
|
16
|
+
readonly token: string;
|
|
17
|
+
readonly tokenSource: "oauth-device" | "manual-token";
|
|
18
|
+
readonly login?: string | null;
|
|
19
|
+
readonly userId?: string | null;
|
|
20
|
+
readonly scopes?: readonly string[];
|
|
21
|
+
readonly selectedRepo?: string | null;
|
|
22
|
+
}) => void;
|
|
23
|
+
readonly createApiSession: () => {
|
|
24
|
+
token: string;
|
|
25
|
+
login: string | null;
|
|
26
|
+
userId: string | null;
|
|
27
|
+
};
|
|
28
|
+
readonly readApiSession: (token: string) => {
|
|
29
|
+
login: string | null;
|
|
30
|
+
userId: string | null;
|
|
31
|
+
} | null;
|
|
32
|
+
readonly copyToProjectRoot: (projectRoot: string) => void;
|
|
33
|
+
readonly copyToLocalProjectRoot: (projectRoot: string) => void;
|
|
34
|
+
readonly savePendingDevice: (input: PendingDeviceFlow) => void;
|
|
35
|
+
readonly saveSelectedRepo: (selectedRepo: string | null) => void;
|
|
36
|
+
readonly readPendingDevice: (pollId: string) => PendingDeviceFlow | null;
|
|
37
|
+
readonly clearPendingDevice: (pollId?: string) => void;
|
|
38
|
+
};
|
|
39
|
+
export declare function resolveGitHubAuthStateFile(projectRoot: string): string;
|
|
40
|
+
export declare function copyGitHubAuthStateToLocalProjectRoot(stateFile: string, projectRoot: string): void;
|
|
41
|
+
export declare function createGitHubAuthStoreFromStateFile(stateFile: string): GitHubAuthStore;
|
|
42
|
+
export declare function createGitHubAuthStore(projectRoot: string): GitHubAuthStore;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/github-provider-plugin/src/auth-store.ts
|
|
3
|
+
import { randomBytes } from "crypto";
|
|
4
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
5
|
+
import { dirname, resolve } from "path";
|
|
6
|
+
import { resolveRigStatePaths } from "@rig/runtime/control-plane/server-paths";
|
|
7
|
+
function cleanString(value) {
|
|
8
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
9
|
+
}
|
|
10
|
+
function cleanScopes(value) {
|
|
11
|
+
if (!Array.isArray(value))
|
|
12
|
+
return [];
|
|
13
|
+
return value.flatMap((entry) => {
|
|
14
|
+
const clean = cleanString(entry);
|
|
15
|
+
return clean ? [clean] : [];
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function parseApiSessions(value) {
|
|
19
|
+
if (!Array.isArray(value))
|
|
20
|
+
return [];
|
|
21
|
+
return value.flatMap((entry) => {
|
|
22
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
23
|
+
return [];
|
|
24
|
+
const record = entry;
|
|
25
|
+
const token = cleanString(record.token);
|
|
26
|
+
if (!token)
|
|
27
|
+
return [];
|
|
28
|
+
return [{
|
|
29
|
+
token,
|
|
30
|
+
login: cleanString(record.login),
|
|
31
|
+
userId: cleanString(record.userId),
|
|
32
|
+
createdAt: cleanString(record.createdAt) ?? undefined
|
|
33
|
+
}];
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function parsePendingDevice(value) {
|
|
37
|
+
if (!value || typeof value !== "object")
|
|
38
|
+
return null;
|
|
39
|
+
const record = value;
|
|
40
|
+
const pollId = cleanString(record.pollId);
|
|
41
|
+
const deviceCode = cleanString(record.deviceCode);
|
|
42
|
+
const expiresAt = cleanString(record.expiresAt);
|
|
43
|
+
const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
|
|
44
|
+
if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
|
|
45
|
+
return null;
|
|
46
|
+
return { pollId, deviceCode, expiresAt, intervalSeconds };
|
|
47
|
+
}
|
|
48
|
+
function parsePendingDevices(value) {
|
|
49
|
+
if (!Array.isArray(value))
|
|
50
|
+
return [];
|
|
51
|
+
return value.flatMap((entry) => {
|
|
52
|
+
const pending = parsePendingDevice(entry);
|
|
53
|
+
return pending ? [pending] : [];
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function readStoredAuth(stateFile) {
|
|
57
|
+
if (!existsSync(stateFile))
|
|
58
|
+
return {};
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(readFileSync(stateFile, "utf8"));
|
|
61
|
+
return {
|
|
62
|
+
...cleanString(parsed.token) ? { token: cleanString(parsed.token) } : {},
|
|
63
|
+
login: cleanString(parsed.login),
|
|
64
|
+
userId: cleanString(parsed.userId),
|
|
65
|
+
scopes: cleanScopes(parsed.scopes),
|
|
66
|
+
selectedRepo: cleanString(parsed.selectedRepo),
|
|
67
|
+
tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
|
|
68
|
+
pendingDevice: parsePendingDevice(parsed.pendingDevice),
|
|
69
|
+
pendingDevices: parsePendingDevices(parsed.pendingDevices),
|
|
70
|
+
apiSessions: parseApiSessions(parsed.apiSessions),
|
|
71
|
+
updatedAt: cleanString(parsed.updatedAt) ?? undefined
|
|
72
|
+
};
|
|
73
|
+
} catch {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function newApiSessionToken() {
|
|
78
|
+
return `rig_${randomBytes(32).toString("base64url")}`;
|
|
79
|
+
}
|
|
80
|
+
function writeStoredAuth(stateFile, payload) {
|
|
81
|
+
mkdirSync(dirname(stateFile), { recursive: true });
|
|
82
|
+
writeFileSync(stateFile, `${JSON.stringify(payload, null, 2)}
|
|
83
|
+
`, { encoding: "utf8", mode: 384 });
|
|
84
|
+
try {
|
|
85
|
+
chmodSync(stateFile, 384);
|
|
86
|
+
} catch {}
|
|
87
|
+
}
|
|
88
|
+
function localProjectAuthStateFile(projectRoot) {
|
|
89
|
+
return resolve(projectRoot, ".rig", "state", "github-auth.json");
|
|
90
|
+
}
|
|
91
|
+
function resolveGitHubAuthStateFile(projectRoot) {
|
|
92
|
+
return resolve(resolveRigStatePaths(projectRoot).stateDir, "github-auth.json");
|
|
93
|
+
}
|
|
94
|
+
function copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot) {
|
|
95
|
+
const targetFile = localProjectAuthStateFile(projectRoot);
|
|
96
|
+
mkdirSync(dirname(targetFile), { recursive: true });
|
|
97
|
+
if (existsSync(stateFile)) {
|
|
98
|
+
copyFileSync(stateFile, targetFile);
|
|
99
|
+
try {
|
|
100
|
+
chmodSync(targetFile, 384);
|
|
101
|
+
} catch {}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
writeStoredAuth(targetFile, {});
|
|
105
|
+
}
|
|
106
|
+
function createGitHubAuthStoreFromStateFile(stateFile) {
|
|
107
|
+
return {
|
|
108
|
+
stateFile,
|
|
109
|
+
status(options) {
|
|
110
|
+
const stored = readStoredAuth(stateFile);
|
|
111
|
+
const token = cleanString(stored.token);
|
|
112
|
+
return {
|
|
113
|
+
signedIn: Boolean(token),
|
|
114
|
+
login: cleanString(stored.login),
|
|
115
|
+
userId: cleanString(stored.userId),
|
|
116
|
+
scopes: cleanScopes(stored.scopes),
|
|
117
|
+
selectedRepo: cleanString(stored.selectedRepo),
|
|
118
|
+
oauthConfigured: options?.oauthConfigured === true,
|
|
119
|
+
tokenSource: token ? stored.tokenSource ?? "manual-token" : null
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
readToken() {
|
|
123
|
+
return cleanString(readStoredAuth(stateFile).token);
|
|
124
|
+
},
|
|
125
|
+
saveToken(input) {
|
|
126
|
+
const previous = readStoredAuth(stateFile);
|
|
127
|
+
writeStoredAuth(stateFile, {
|
|
128
|
+
...previous,
|
|
129
|
+
token: input.token,
|
|
130
|
+
tokenSource: input.tokenSource,
|
|
131
|
+
login: input.login ?? null,
|
|
132
|
+
userId: input.userId ?? null,
|
|
133
|
+
scopes: input.scopes ?? [],
|
|
134
|
+
selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
|
|
135
|
+
pendingDevice: null,
|
|
136
|
+
pendingDevices: [],
|
|
137
|
+
apiSessions: previous.apiSessions ?? [],
|
|
138
|
+
updatedAt: new Date().toISOString()
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
createApiSession() {
|
|
142
|
+
const previous = readStoredAuth(stateFile);
|
|
143
|
+
const token = newApiSessionToken();
|
|
144
|
+
const session = {
|
|
145
|
+
token,
|
|
146
|
+
login: cleanString(previous.login),
|
|
147
|
+
userId: cleanString(previous.userId),
|
|
148
|
+
createdAt: new Date().toISOString()
|
|
149
|
+
};
|
|
150
|
+
writeStoredAuth(stateFile, {
|
|
151
|
+
...previous,
|
|
152
|
+
apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
|
|
153
|
+
updatedAt: new Date().toISOString()
|
|
154
|
+
});
|
|
155
|
+
return { token, login: session.login ?? null, userId: session.userId ?? null };
|
|
156
|
+
},
|
|
157
|
+
readApiSession(token) {
|
|
158
|
+
const clean = cleanString(token);
|
|
159
|
+
if (!clean)
|
|
160
|
+
return null;
|
|
161
|
+
const previous = readStoredAuth(stateFile);
|
|
162
|
+
const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
|
|
163
|
+
return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
|
|
164
|
+
},
|
|
165
|
+
copyToProjectRoot(projectRoot) {
|
|
166
|
+
const targetFile = resolveGitHubAuthStateFile(projectRoot);
|
|
167
|
+
writeStoredAuth(targetFile, readStoredAuth(stateFile));
|
|
168
|
+
},
|
|
169
|
+
copyToLocalProjectRoot(projectRoot) {
|
|
170
|
+
copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot);
|
|
171
|
+
},
|
|
172
|
+
savePendingDevice(input) {
|
|
173
|
+
const previous = readStoredAuth(stateFile);
|
|
174
|
+
const pendingDevices = [
|
|
175
|
+
...previous.pendingDevice ? [previous.pendingDevice] : [],
|
|
176
|
+
...previous.pendingDevices ?? [],
|
|
177
|
+
input
|
|
178
|
+
].filter((entry, index, entries) => entries.findIndex((candidate) => candidate.pollId === entry.pollId) === index);
|
|
179
|
+
writeStoredAuth(stateFile, {
|
|
180
|
+
...previous,
|
|
181
|
+
pendingDevice: null,
|
|
182
|
+
pendingDevices,
|
|
183
|
+
updatedAt: new Date().toISOString()
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
saveSelectedRepo(selectedRepo) {
|
|
187
|
+
const previous = readStoredAuth(stateFile);
|
|
188
|
+
writeStoredAuth(stateFile, {
|
|
189
|
+
...previous,
|
|
190
|
+
selectedRepo: selectedRepo ?? null,
|
|
191
|
+
updatedAt: new Date().toISOString()
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
readPendingDevice(pollId) {
|
|
195
|
+
const previous = readStoredAuth(stateFile);
|
|
196
|
+
const pending = [
|
|
197
|
+
...previous.pendingDevice ? [previous.pendingDevice] : [],
|
|
198
|
+
...previous.pendingDevices ?? []
|
|
199
|
+
].find((entry) => entry.pollId === pollId) ?? null;
|
|
200
|
+
if (!pending)
|
|
201
|
+
return null;
|
|
202
|
+
if (Date.parse(pending.expiresAt) <= Date.now())
|
|
203
|
+
return null;
|
|
204
|
+
return pending;
|
|
205
|
+
},
|
|
206
|
+
clearPendingDevice(pollId) {
|
|
207
|
+
const previous = readStoredAuth(stateFile);
|
|
208
|
+
const remaining = pollId ? (previous.pendingDevices ?? []).filter((entry) => entry.pollId !== pollId) : [];
|
|
209
|
+
writeStoredAuth(stateFile, {
|
|
210
|
+
...previous,
|
|
211
|
+
pendingDevice: null,
|
|
212
|
+
pendingDevices: remaining,
|
|
213
|
+
updatedAt: new Date().toISOString()
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function createGitHubAuthStore(projectRoot) {
|
|
219
|
+
return createGitHubAuthStoreFromStateFile(resolveGitHubAuthStateFile(projectRoot));
|
|
220
|
+
}
|
|
221
|
+
export {
|
|
222
|
+
resolveGitHubAuthStateFile,
|
|
223
|
+
createGitHubAuthStoreFromStateFile,
|
|
224
|
+
createGitHubAuthStore,
|
|
225
|
+
copyGitHubAuthStateToLocalProjectRoot
|
|
226
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { GitHubCredentialProvider, GitHubCredentialProviderOptions, GitHubStateCredentialProviderOptions } from "@rig/contracts";
|
|
2
|
+
export type { GitHubCredentialProvider, GitHubCredentialProviderOptions, GitHubCredentialPurpose, GitHubCredentialSource, GitHubStateCredentialProviderOptions, ResolveGitHubTokenInput, ResolvedGitHubToken, } from "@rig/contracts";
|
|
3
|
+
/**
|
|
4
|
+
* Session-token-map credential provider. Selected-repo operations resolve from
|
|
5
|
+
* the per-session token map; admin-fallback resolves from the host token.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createGitHubCredentialProvider(options?: GitHubCredentialProviderOptions): GitHubCredentialProvider;
|
|
8
|
+
/**
|
|
9
|
+
* Env-only credential provider. Selected-repo reads `RIG_GITHUB_SELECTED_TOKEN`
|
|
10
|
+
* (browser-selected public repos read without a token); admin-fallback reads the
|
|
11
|
+
* host token env vars. Consolidated here from task-sources so the credential
|
|
12
|
+
* abstraction has a single home.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createEnvGitHubCredentialProvider(): GitHubCredentialProvider;
|
|
15
|
+
/**
|
|
16
|
+
* State-file-backed credential provider. Resolves the signed-in token from a
|
|
17
|
+
* `github-auth.json` discovered across explicit/state/project-root candidates,
|
|
18
|
+
* falling back to host token env vars. Consolidated here from task-sources.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createStateGitHubCredentialProvider(options?: GitHubStateCredentialProviderOptions): GitHubCredentialProvider;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/github-provider-plugin/src/credentials.ts
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
import { resolve } from "path";
|
|
5
|
+
function selectedRepoTokenKey(input) {
|
|
6
|
+
return `user:${input.userId}|repo:${input.owner}/${input.repo}|workspace:${input.workspaceId}`;
|
|
7
|
+
}
|
|
8
|
+
function cleanToken(value) {
|
|
9
|
+
const trimmed = value?.trim() ?? "";
|
|
10
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
11
|
+
}
|
|
12
|
+
function createGitHubCredentialProvider(options = {}) {
|
|
13
|
+
const sessionTokens = options.sessionTokens ?? {};
|
|
14
|
+
const hostToken = cleanToken(options.hostToken ?? process.env.GH_TOKEN ?? null);
|
|
15
|
+
return {
|
|
16
|
+
async resolveGitHubToken(input) {
|
|
17
|
+
const owner = input.owner.trim();
|
|
18
|
+
const repo = input.repo.trim();
|
|
19
|
+
const workspaceId = input.workspaceId.trim();
|
|
20
|
+
const userId = input.userId?.trim() ?? "";
|
|
21
|
+
if (input.purpose === "selected-repo") {
|
|
22
|
+
if (!owner || !repo || !workspaceId || !userId) {
|
|
23
|
+
throw new Error("No signed-in GitHub token is available for the selected repo; sign in to GitHub for this workspace.");
|
|
24
|
+
}
|
|
25
|
+
const token = cleanToken(sessionTokens[selectedRepoTokenKey({ owner, repo, workspaceId, userId })]);
|
|
26
|
+
if (!token) {
|
|
27
|
+
throw new Error("No signed-in GitHub token is available for the selected repo; sign in to GitHub for this workspace.");
|
|
28
|
+
}
|
|
29
|
+
return { token, source: "signed-in-user" };
|
|
30
|
+
}
|
|
31
|
+
if (hostToken) {
|
|
32
|
+
return { token: hostToken, source: "host-admin-fallback" };
|
|
33
|
+
}
|
|
34
|
+
throw new Error("No host GitHub token is configured for the explicit admin fallback operation.");
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function createEnvGitHubCredentialProvider() {
|
|
39
|
+
return {
|
|
40
|
+
async resolveGitHubToken(input) {
|
|
41
|
+
if (input.purpose === "selected-repo") {
|
|
42
|
+
return { token: cleanToken(process.env.RIG_GITHUB_SELECTED_TOKEN ?? null) ?? "", source: "signed-in-user" };
|
|
43
|
+
}
|
|
44
|
+
const token = cleanToken(process.env.RIG_GITHUB_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
|
|
45
|
+
if (!token) {
|
|
46
|
+
throw new Error("No host GitHub token is configured for admin fallback.");
|
|
47
|
+
}
|
|
48
|
+
return { token, source: "host-admin-fallback" };
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function createStateGitHubCredentialProvider(options = {}) {
|
|
53
|
+
const addCandidate = (candidates, path) => {
|
|
54
|
+
const trimmed = path?.trim();
|
|
55
|
+
if (!trimmed)
|
|
56
|
+
return;
|
|
57
|
+
const resolved = resolve(trimmed);
|
|
58
|
+
if (!candidates.includes(resolved))
|
|
59
|
+
candidates.push(resolved);
|
|
60
|
+
};
|
|
61
|
+
const addStateDir = (candidates, dir) => {
|
|
62
|
+
const trimmed = dir?.trim();
|
|
63
|
+
if (!trimmed)
|
|
64
|
+
return;
|
|
65
|
+
addCandidate(candidates, resolve(trimmed, "github-auth.json"));
|
|
66
|
+
};
|
|
67
|
+
const addProjectStateDir = (candidates, root) => {
|
|
68
|
+
const trimmed = root?.trim();
|
|
69
|
+
if (!trimmed)
|
|
70
|
+
return;
|
|
71
|
+
addStateDir(candidates, resolve(trimmed, ".rig", "state"));
|
|
72
|
+
};
|
|
73
|
+
const stateFileCandidates = () => {
|
|
74
|
+
const candidates = [];
|
|
75
|
+
addCandidate(candidates, options.stateFile ?? process.env.RIG_GITHUB_AUTH_STATE_FILE);
|
|
76
|
+
addStateDir(candidates, options.stateDir);
|
|
77
|
+
addStateDir(candidates, process.env.RIG_STATE_DIR);
|
|
78
|
+
addProjectStateDir(candidates, process.env.PROJECT_RIG_ROOT);
|
|
79
|
+
addProjectStateDir(candidates, process.env.RIG_PROJECT_ROOT);
|
|
80
|
+
addProjectStateDir(candidates, process.env.RIG_HOST_PROJECT_ROOT);
|
|
81
|
+
addProjectStateDir(candidates, process.cwd());
|
|
82
|
+
return candidates;
|
|
83
|
+
};
|
|
84
|
+
const readToken = () => {
|
|
85
|
+
for (const stateFile of stateFileCandidates()) {
|
|
86
|
+
if (!existsSync(stateFile))
|
|
87
|
+
continue;
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(readFileSync(stateFile, "utf8"));
|
|
90
|
+
const token = typeof parsed.token === "string" ? cleanToken(parsed.token) : null;
|
|
91
|
+
if (token)
|
|
92
|
+
return token;
|
|
93
|
+
} catch {}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
async resolveGitHubToken(input) {
|
|
99
|
+
const token = readToken();
|
|
100
|
+
if (input.purpose === "selected-repo") {
|
|
101
|
+
return { token: token ?? cleanToken(process.env.RIG_GITHUB_SELECTED_TOKEN ?? null) ?? "", source: "signed-in-user" };
|
|
102
|
+
}
|
|
103
|
+
if (token) {
|
|
104
|
+
return { token, source: "signed-in-user" };
|
|
105
|
+
}
|
|
106
|
+
const fallback = cleanToken(process.env.RIG_GITHUB_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
|
|
107
|
+
if (!fallback) {
|
|
108
|
+
throw new Error("No signed-in GitHub token is stored for Rig and no host admin fallback token is configured.");
|
|
109
|
+
}
|
|
110
|
+
return { token: fallback, source: "host-admin-fallback" };
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
export {
|
|
115
|
+
createStateGitHubCredentialProvider,
|
|
116
|
+
createGitHubCredentialProvider,
|
|
117
|
+
createEnvGitHubCredentialProvider
|
|
118
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { GitHubRepositoryProbe, GitHubUserInfo } from "@rig/contracts";
|
|
2
|
+
export type { GitHubRepositoryProbe, GitHubUserInfo };
|
|
3
|
+
export type GitHubFormPostResult = {
|
|
4
|
+
readonly status: number;
|
|
5
|
+
readonly payload: Record<string, unknown>;
|
|
6
|
+
};
|
|
7
|
+
export type GitHubFormPoster = (endpoint: string, body: Record<string, string>) => Promise<GitHubFormPostResult>;
|
|
8
|
+
export type GitHubUserFetcher = (token: string) => Promise<GitHubUserInfo>;
|
|
9
|
+
export declare function fetchGitHubUserInfo(token: string): Promise<GitHubUserInfo>;
|
|
10
|
+
export declare function resolveGitHubAuthStatus(input: {
|
|
11
|
+
readonly projectRoot: string;
|
|
12
|
+
readonly oauthConfigured?: boolean;
|
|
13
|
+
}): import("@rig/contracts").GitHubAuthStatus;
|
|
14
|
+
export declare function saveGitHubTokenForProject(input: {
|
|
15
|
+
readonly projectRoot: string;
|
|
16
|
+
readonly token: string;
|
|
17
|
+
readonly tokenSource?: "oauth-device" | "manual-token";
|
|
18
|
+
readonly selectedRepo?: string | null;
|
|
19
|
+
readonly fetchUser?: GitHubUserFetcher;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
signedIn: boolean;
|
|
22
|
+
login: string | null;
|
|
23
|
+
userId: string | null;
|
|
24
|
+
scopes: readonly string[];
|
|
25
|
+
selectedRepo: string | null;
|
|
26
|
+
oauthConfigured: boolean;
|
|
27
|
+
tokenSource: "oauth-device" | "manual-token" | "env" | null;
|
|
28
|
+
ok: true;
|
|
29
|
+
}>;
|
|
30
|
+
export declare function beginGitHubDeviceFlow(input: {
|
|
31
|
+
readonly projectRoot: string;
|
|
32
|
+
readonly clientId: string;
|
|
33
|
+
readonly scope?: string;
|
|
34
|
+
readonly selectedRepo?: string | null;
|
|
35
|
+
readonly postForm?: GitHubFormPoster;
|
|
36
|
+
}): Promise<{
|
|
37
|
+
ok: true;
|
|
38
|
+
pollId: `${string}-${string}-${string}-${string}-${string}`;
|
|
39
|
+
userCode: string | null;
|
|
40
|
+
verificationUri: string | null;
|
|
41
|
+
expiresIn: number;
|
|
42
|
+
intervalSeconds: number;
|
|
43
|
+
}>;
|
|
44
|
+
export declare function pollGitHubDeviceFlow(input: {
|
|
45
|
+
readonly projectRoot: string;
|
|
46
|
+
readonly clientId: string;
|
|
47
|
+
readonly pollId: string;
|
|
48
|
+
readonly selectedRepo?: string | null;
|
|
49
|
+
readonly postForm?: GitHubFormPoster;
|
|
50
|
+
readonly fetchUser?: GitHubUserFetcher;
|
|
51
|
+
}): Promise<{
|
|
52
|
+
ok: false;
|
|
53
|
+
status: "expired";
|
|
54
|
+
error: string;
|
|
55
|
+
intervalSeconds?: undefined;
|
|
56
|
+
} | {
|
|
57
|
+
ok: false;
|
|
58
|
+
status: "pending" | "slow-down";
|
|
59
|
+
intervalSeconds: number;
|
|
60
|
+
error?: undefined;
|
|
61
|
+
} | {
|
|
62
|
+
ok: false;
|
|
63
|
+
status: "error";
|
|
64
|
+
error: string;
|
|
65
|
+
intervalSeconds?: undefined;
|
|
66
|
+
} | {
|
|
67
|
+
signedIn: boolean;
|
|
68
|
+
login: string | null;
|
|
69
|
+
userId: string | null;
|
|
70
|
+
scopes: readonly string[];
|
|
71
|
+
selectedRepo: string | null;
|
|
72
|
+
oauthConfigured: boolean;
|
|
73
|
+
tokenSource: "oauth-device" | "manual-token" | "env" | null;
|
|
74
|
+
ok: true;
|
|
75
|
+
status: "signed-in";
|
|
76
|
+
error?: undefined;
|
|
77
|
+
intervalSeconds?: undefined;
|
|
78
|
+
}>;
|
|
79
|
+
export declare function checkGitHubRepoPermissions(input: {
|
|
80
|
+
readonly projectRoot: string;
|
|
81
|
+
readonly oauthConfigured?: boolean;
|
|
82
|
+
}): {
|
|
83
|
+
ok: false;
|
|
84
|
+
signedIn: boolean;
|
|
85
|
+
canOpenPullRequest: boolean;
|
|
86
|
+
reason: "not-authenticated";
|
|
87
|
+
login?: undefined;
|
|
88
|
+
scopes?: undefined;
|
|
89
|
+
pullRequests?: undefined;
|
|
90
|
+
push?: undefined;
|
|
91
|
+
} | {
|
|
92
|
+
ok: true;
|
|
93
|
+
signedIn: boolean;
|
|
94
|
+
login: string | null;
|
|
95
|
+
scopes: readonly string[];
|
|
96
|
+
canOpenPullRequest: boolean;
|
|
97
|
+
pullRequests: boolean;
|
|
98
|
+
push: boolean;
|
|
99
|
+
reason: "stored-token" | "token-scope-unverified";
|
|
100
|
+
};
|
|
101
|
+
export declare function probeGitHubRepository(input: {
|
|
102
|
+
readonly owner: string;
|
|
103
|
+
readonly repo: string;
|
|
104
|
+
readonly token: string | null;
|
|
105
|
+
readonly scopes: readonly string[];
|
|
106
|
+
readonly fetchRepository?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
107
|
+
}): Promise<GitHubRepositoryProbe>;
|