@h-rig/github-provider-plugin 0.0.6-alpha.157 → 0.0.6-alpha.158
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/src/identity-env.d.ts +16 -0
- package/dist/src/identity-env.js +239 -0
- package/dist/src/identity.d.ts +17 -0
- package/dist/src/identity.js +134 -0
- package/dist/src/index.d.ts +1 -4
- package/dist/src/index.js +360 -740
- package/dist/src/issue-analysis.d.ts +5 -5
- package/dist/src/issue-analysis.js +6 -6
- package/dist/src/lib.d.ts +16 -0
- package/dist/src/lib.js +485 -0
- package/dist/src/plugin.d.ts +6 -2
- package/dist/src/plugin.js +792 -31
- package/dist/src/profile-ops.d.ts +8 -0
- package/dist/src/profile-ops.js +9 -0
- package/dist/src/service.js +1 -35
- package/dist/src/token-env.d.ts +3 -0
- package/dist/src/token-env.js +26 -0
- package/dist/src/triage-run.js +12 -10
- package/package.json +24 -4
- package/dist/src/auth-store.d.ts +0 -42
- package/dist/src/auth-store.js +0 -226
- package/dist/src/credentials.d.ts +0 -20
- package/dist/src/credentials.js +0 -118
- package/dist/src/github-api.d.ts +0 -107
- package/dist/src/github-api.js +0 -451
- package/dist/src/projects.d.ts +0 -31
- package/dist/src/projects.js +0 -147
package/dist/src/github-api.d.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
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>;
|
package/dist/src/github-api.js
DELETED
|
@@ -1,451 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// packages/github-provider-plugin/src/github-api.ts
|
|
3
|
-
import { randomUUID } from "crypto";
|
|
4
|
-
|
|
5
|
-
// packages/github-provider-plugin/src/auth-store.ts
|
|
6
|
-
import { randomBytes } from "crypto";
|
|
7
|
-
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
8
|
-
import { dirname, resolve } from "path";
|
|
9
|
-
import { resolveRigStatePaths } from "@rig/runtime/control-plane/server-paths";
|
|
10
|
-
function cleanString(value) {
|
|
11
|
-
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
12
|
-
}
|
|
13
|
-
function cleanScopes(value) {
|
|
14
|
-
if (!Array.isArray(value))
|
|
15
|
-
return [];
|
|
16
|
-
return value.flatMap((entry) => {
|
|
17
|
-
const clean = cleanString(entry);
|
|
18
|
-
return clean ? [clean] : [];
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
function parseApiSessions(value) {
|
|
22
|
-
if (!Array.isArray(value))
|
|
23
|
-
return [];
|
|
24
|
-
return value.flatMap((entry) => {
|
|
25
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
26
|
-
return [];
|
|
27
|
-
const record = entry;
|
|
28
|
-
const token = cleanString(record.token);
|
|
29
|
-
if (!token)
|
|
30
|
-
return [];
|
|
31
|
-
return [{
|
|
32
|
-
token,
|
|
33
|
-
login: cleanString(record.login),
|
|
34
|
-
userId: cleanString(record.userId),
|
|
35
|
-
createdAt: cleanString(record.createdAt) ?? undefined
|
|
36
|
-
}];
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
function parsePendingDevice(value) {
|
|
40
|
-
if (!value || typeof value !== "object")
|
|
41
|
-
return null;
|
|
42
|
-
const record = value;
|
|
43
|
-
const pollId = cleanString(record.pollId);
|
|
44
|
-
const deviceCode = cleanString(record.deviceCode);
|
|
45
|
-
const expiresAt = cleanString(record.expiresAt);
|
|
46
|
-
const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
|
|
47
|
-
if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
|
|
48
|
-
return null;
|
|
49
|
-
return { pollId, deviceCode, expiresAt, intervalSeconds };
|
|
50
|
-
}
|
|
51
|
-
function parsePendingDevices(value) {
|
|
52
|
-
if (!Array.isArray(value))
|
|
53
|
-
return [];
|
|
54
|
-
return value.flatMap((entry) => {
|
|
55
|
-
const pending = parsePendingDevice(entry);
|
|
56
|
-
return pending ? [pending] : [];
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
function readStoredAuth(stateFile) {
|
|
60
|
-
if (!existsSync(stateFile))
|
|
61
|
-
return {};
|
|
62
|
-
try {
|
|
63
|
-
const parsed = JSON.parse(readFileSync(stateFile, "utf8"));
|
|
64
|
-
return {
|
|
65
|
-
...cleanString(parsed.token) ? { token: cleanString(parsed.token) } : {},
|
|
66
|
-
login: cleanString(parsed.login),
|
|
67
|
-
userId: cleanString(parsed.userId),
|
|
68
|
-
scopes: cleanScopes(parsed.scopes),
|
|
69
|
-
selectedRepo: cleanString(parsed.selectedRepo),
|
|
70
|
-
tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
|
|
71
|
-
pendingDevice: parsePendingDevice(parsed.pendingDevice),
|
|
72
|
-
pendingDevices: parsePendingDevices(parsed.pendingDevices),
|
|
73
|
-
apiSessions: parseApiSessions(parsed.apiSessions),
|
|
74
|
-
updatedAt: cleanString(parsed.updatedAt) ?? undefined
|
|
75
|
-
};
|
|
76
|
-
} catch {
|
|
77
|
-
return {};
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function newApiSessionToken() {
|
|
81
|
-
return `rig_${randomBytes(32).toString("base64url")}`;
|
|
82
|
-
}
|
|
83
|
-
function writeStoredAuth(stateFile, payload) {
|
|
84
|
-
mkdirSync(dirname(stateFile), { recursive: true });
|
|
85
|
-
writeFileSync(stateFile, `${JSON.stringify(payload, null, 2)}
|
|
86
|
-
`, { encoding: "utf8", mode: 384 });
|
|
87
|
-
try {
|
|
88
|
-
chmodSync(stateFile, 384);
|
|
89
|
-
} catch {}
|
|
90
|
-
}
|
|
91
|
-
function localProjectAuthStateFile(projectRoot) {
|
|
92
|
-
return resolve(projectRoot, ".rig", "state", "github-auth.json");
|
|
93
|
-
}
|
|
94
|
-
function resolveGitHubAuthStateFile(projectRoot) {
|
|
95
|
-
return resolve(resolveRigStatePaths(projectRoot).stateDir, "github-auth.json");
|
|
96
|
-
}
|
|
97
|
-
function copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot) {
|
|
98
|
-
const targetFile = localProjectAuthStateFile(projectRoot);
|
|
99
|
-
mkdirSync(dirname(targetFile), { recursive: true });
|
|
100
|
-
if (existsSync(stateFile)) {
|
|
101
|
-
copyFileSync(stateFile, targetFile);
|
|
102
|
-
try {
|
|
103
|
-
chmodSync(targetFile, 384);
|
|
104
|
-
} catch {}
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
writeStoredAuth(targetFile, {});
|
|
108
|
-
}
|
|
109
|
-
function createGitHubAuthStoreFromStateFile(stateFile) {
|
|
110
|
-
return {
|
|
111
|
-
stateFile,
|
|
112
|
-
status(options) {
|
|
113
|
-
const stored = readStoredAuth(stateFile);
|
|
114
|
-
const token = cleanString(stored.token);
|
|
115
|
-
return {
|
|
116
|
-
signedIn: Boolean(token),
|
|
117
|
-
login: cleanString(stored.login),
|
|
118
|
-
userId: cleanString(stored.userId),
|
|
119
|
-
scopes: cleanScopes(stored.scopes),
|
|
120
|
-
selectedRepo: cleanString(stored.selectedRepo),
|
|
121
|
-
oauthConfigured: options?.oauthConfigured === true,
|
|
122
|
-
tokenSource: token ? stored.tokenSource ?? "manual-token" : null
|
|
123
|
-
};
|
|
124
|
-
},
|
|
125
|
-
readToken() {
|
|
126
|
-
return cleanString(readStoredAuth(stateFile).token);
|
|
127
|
-
},
|
|
128
|
-
saveToken(input) {
|
|
129
|
-
const previous = readStoredAuth(stateFile);
|
|
130
|
-
writeStoredAuth(stateFile, {
|
|
131
|
-
...previous,
|
|
132
|
-
token: input.token,
|
|
133
|
-
tokenSource: input.tokenSource,
|
|
134
|
-
login: input.login ?? null,
|
|
135
|
-
userId: input.userId ?? null,
|
|
136
|
-
scopes: input.scopes ?? [],
|
|
137
|
-
selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
|
|
138
|
-
pendingDevice: null,
|
|
139
|
-
pendingDevices: [],
|
|
140
|
-
apiSessions: previous.apiSessions ?? [],
|
|
141
|
-
updatedAt: new Date().toISOString()
|
|
142
|
-
});
|
|
143
|
-
},
|
|
144
|
-
createApiSession() {
|
|
145
|
-
const previous = readStoredAuth(stateFile);
|
|
146
|
-
const token = newApiSessionToken();
|
|
147
|
-
const session = {
|
|
148
|
-
token,
|
|
149
|
-
login: cleanString(previous.login),
|
|
150
|
-
userId: cleanString(previous.userId),
|
|
151
|
-
createdAt: new Date().toISOString()
|
|
152
|
-
};
|
|
153
|
-
writeStoredAuth(stateFile, {
|
|
154
|
-
...previous,
|
|
155
|
-
apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
|
|
156
|
-
updatedAt: new Date().toISOString()
|
|
157
|
-
});
|
|
158
|
-
return { token, login: session.login ?? null, userId: session.userId ?? null };
|
|
159
|
-
},
|
|
160
|
-
readApiSession(token) {
|
|
161
|
-
const clean = cleanString(token);
|
|
162
|
-
if (!clean)
|
|
163
|
-
return null;
|
|
164
|
-
const previous = readStoredAuth(stateFile);
|
|
165
|
-
const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
|
|
166
|
-
return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
|
|
167
|
-
},
|
|
168
|
-
copyToProjectRoot(projectRoot) {
|
|
169
|
-
const targetFile = resolveGitHubAuthStateFile(projectRoot);
|
|
170
|
-
writeStoredAuth(targetFile, readStoredAuth(stateFile));
|
|
171
|
-
},
|
|
172
|
-
copyToLocalProjectRoot(projectRoot) {
|
|
173
|
-
copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot);
|
|
174
|
-
},
|
|
175
|
-
savePendingDevice(input) {
|
|
176
|
-
const previous = readStoredAuth(stateFile);
|
|
177
|
-
const pendingDevices = [
|
|
178
|
-
...previous.pendingDevice ? [previous.pendingDevice] : [],
|
|
179
|
-
...previous.pendingDevices ?? [],
|
|
180
|
-
input
|
|
181
|
-
].filter((entry, index, entries) => entries.findIndex((candidate) => candidate.pollId === entry.pollId) === index);
|
|
182
|
-
writeStoredAuth(stateFile, {
|
|
183
|
-
...previous,
|
|
184
|
-
pendingDevice: null,
|
|
185
|
-
pendingDevices,
|
|
186
|
-
updatedAt: new Date().toISOString()
|
|
187
|
-
});
|
|
188
|
-
},
|
|
189
|
-
saveSelectedRepo(selectedRepo) {
|
|
190
|
-
const previous = readStoredAuth(stateFile);
|
|
191
|
-
writeStoredAuth(stateFile, {
|
|
192
|
-
...previous,
|
|
193
|
-
selectedRepo: selectedRepo ?? null,
|
|
194
|
-
updatedAt: new Date().toISOString()
|
|
195
|
-
});
|
|
196
|
-
},
|
|
197
|
-
readPendingDevice(pollId) {
|
|
198
|
-
const previous = readStoredAuth(stateFile);
|
|
199
|
-
const pending = [
|
|
200
|
-
...previous.pendingDevice ? [previous.pendingDevice] : [],
|
|
201
|
-
...previous.pendingDevices ?? []
|
|
202
|
-
].find((entry) => entry.pollId === pollId) ?? null;
|
|
203
|
-
if (!pending)
|
|
204
|
-
return null;
|
|
205
|
-
if (Date.parse(pending.expiresAt) <= Date.now())
|
|
206
|
-
return null;
|
|
207
|
-
return pending;
|
|
208
|
-
},
|
|
209
|
-
clearPendingDevice(pollId) {
|
|
210
|
-
const previous = readStoredAuth(stateFile);
|
|
211
|
-
const remaining = pollId ? (previous.pendingDevices ?? []).filter((entry) => entry.pollId !== pollId) : [];
|
|
212
|
-
writeStoredAuth(stateFile, {
|
|
213
|
-
...previous,
|
|
214
|
-
pendingDevice: null,
|
|
215
|
-
pendingDevices: remaining,
|
|
216
|
-
updatedAt: new Date().toISOString()
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
function createGitHubAuthStore(projectRoot) {
|
|
222
|
-
return createGitHubAuthStoreFromStateFile(resolveGitHubAuthStateFile(projectRoot));
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// packages/github-provider-plugin/src/github-api.ts
|
|
226
|
-
function normalizeString(value) {
|
|
227
|
-
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
228
|
-
}
|
|
229
|
-
function normalizeScopes(value) {
|
|
230
|
-
if (Array.isArray(value)) {
|
|
231
|
-
return value.flatMap((entry) => {
|
|
232
|
-
const normalized = normalizeString(entry);
|
|
233
|
-
return normalized ? [normalized] : [];
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
if (typeof value === "string") {
|
|
237
|
-
return value.split(/[ ,]+/).map((entry) => entry.trim()).filter(Boolean);
|
|
238
|
-
}
|
|
239
|
-
return [];
|
|
240
|
-
}
|
|
241
|
-
async function defaultPostGitHubForm(endpoint, body) {
|
|
242
|
-
const response = await fetch(endpoint, {
|
|
243
|
-
method: "POST",
|
|
244
|
-
headers: {
|
|
245
|
-
accept: "application/json",
|
|
246
|
-
"content-type": "application/x-www-form-urlencoded",
|
|
247
|
-
"user-agent": "rig"
|
|
248
|
-
},
|
|
249
|
-
body: new URLSearchParams(body)
|
|
250
|
-
});
|
|
251
|
-
const payload = await response.json().catch(() => ({}));
|
|
252
|
-
return { status: response.status, payload };
|
|
253
|
-
}
|
|
254
|
-
async function fetchGitHubUserInfo(token) {
|
|
255
|
-
const response = await fetch("https://api.github.com/user", {
|
|
256
|
-
headers: {
|
|
257
|
-
accept: "application/vnd.github+json",
|
|
258
|
-
authorization: `Bearer ${token}`,
|
|
259
|
-
"user-agent": "rig"
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
const payload = await response.json().catch(() => ({}));
|
|
263
|
-
if (!response.ok) {
|
|
264
|
-
throw new Error(typeof payload.message === "string" ? payload.message : `GitHub user lookup failed (${response.status}).`);
|
|
265
|
-
}
|
|
266
|
-
const login = normalizeString(payload.login);
|
|
267
|
-
const id = typeof payload.id === "number" ? String(payload.id) : normalizeString(payload.id);
|
|
268
|
-
if (!login || !id) {
|
|
269
|
-
throw new Error("GitHub user lookup did not return login/id.");
|
|
270
|
-
}
|
|
271
|
-
return { login, id, scopes: normalizeScopes(response.headers.get("x-oauth-scopes")) };
|
|
272
|
-
}
|
|
273
|
-
function resolveGitHubAuthStatus(input) {
|
|
274
|
-
return createGitHubAuthStore(input.projectRoot).status({ oauthConfigured: input.oauthConfigured });
|
|
275
|
-
}
|
|
276
|
-
async function saveGitHubTokenForProject(input) {
|
|
277
|
-
const token = normalizeString(input.token);
|
|
278
|
-
if (!token) {
|
|
279
|
-
throw new Error("token is required");
|
|
280
|
-
}
|
|
281
|
-
const user = await (input.fetchUser ?? fetchGitHubUserInfo)(token);
|
|
282
|
-
const store = createGitHubAuthStore(input.projectRoot);
|
|
283
|
-
store.saveToken({
|
|
284
|
-
token,
|
|
285
|
-
tokenSource: input.tokenSource ?? "manual-token",
|
|
286
|
-
login: user.login,
|
|
287
|
-
userId: user.id,
|
|
288
|
-
scopes: user.scopes ?? [],
|
|
289
|
-
selectedRepo: input.selectedRepo ?? undefined
|
|
290
|
-
});
|
|
291
|
-
return { ok: true, ...store.status({ oauthConfigured: true }) };
|
|
292
|
-
}
|
|
293
|
-
async function beginGitHubDeviceFlow(input) {
|
|
294
|
-
const clientId = normalizeString(input.clientId);
|
|
295
|
-
if (!clientId) {
|
|
296
|
-
throw new Error("clientId is required");
|
|
297
|
-
}
|
|
298
|
-
const postForm = input.postForm ?? defaultPostGitHubForm;
|
|
299
|
-
const result = await postForm("https://github.com/login/device/code", {
|
|
300
|
-
client_id: clientId,
|
|
301
|
-
scope: normalizeString(input.scope) ?? "repo read:project user:email"
|
|
302
|
-
});
|
|
303
|
-
const deviceCode = normalizeString(result.payload.device_code);
|
|
304
|
-
if (result.status < 200 || result.status >= 300 || !deviceCode) {
|
|
305
|
-
throw new Error(normalizeString(result.payload.error_description) ?? "GitHub device flow start failed.");
|
|
306
|
-
}
|
|
307
|
-
const expiresIn = typeof result.payload.expires_in === "number" ? result.payload.expires_in : 900;
|
|
308
|
-
const intervalSeconds = typeof result.payload.interval === "number" ? result.payload.interval : 5;
|
|
309
|
-
const pollId = randomUUID();
|
|
310
|
-
createGitHubAuthStore(input.projectRoot).savePendingDevice({
|
|
311
|
-
pollId,
|
|
312
|
-
deviceCode,
|
|
313
|
-
expiresAt: new Date(Date.now() + expiresIn * 1000).toISOString(),
|
|
314
|
-
intervalSeconds
|
|
315
|
-
});
|
|
316
|
-
if (input.selectedRepo) {
|
|
317
|
-
createGitHubAuthStore(input.projectRoot).saveSelectedRepo(input.selectedRepo);
|
|
318
|
-
}
|
|
319
|
-
return {
|
|
320
|
-
ok: true,
|
|
321
|
-
pollId,
|
|
322
|
-
userCode: normalizeString(result.payload.user_code),
|
|
323
|
-
verificationUri: normalizeString(result.payload.verification_uri),
|
|
324
|
-
expiresIn,
|
|
325
|
-
intervalSeconds
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
async function pollGitHubDeviceFlow(input) {
|
|
329
|
-
const clientId = normalizeString(input.clientId);
|
|
330
|
-
if (!clientId) {
|
|
331
|
-
throw new Error("clientId is required");
|
|
332
|
-
}
|
|
333
|
-
const store = createGitHubAuthStore(input.projectRoot);
|
|
334
|
-
const pending = store.readPendingDevice(input.pollId);
|
|
335
|
-
if (!pending) {
|
|
336
|
-
return { ok: false, status: "expired", error: "GitHub device flow expired or unknown." };
|
|
337
|
-
}
|
|
338
|
-
const result = await (input.postForm ?? defaultPostGitHubForm)("https://github.com/login/oauth/access_token", {
|
|
339
|
-
client_id: clientId,
|
|
340
|
-
device_code: pending.deviceCode,
|
|
341
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
342
|
-
});
|
|
343
|
-
const error = normalizeString(result.payload.error);
|
|
344
|
-
if (error === "authorization_pending" || error === "slow_down") {
|
|
345
|
-
return {
|
|
346
|
-
ok: false,
|
|
347
|
-
status: error === "slow_down" ? "slow-down" : "pending",
|
|
348
|
-
intervalSeconds: error === "slow_down" ? pending.intervalSeconds + 5 : pending.intervalSeconds
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
if (error || typeof result.payload.access_token !== "string") {
|
|
352
|
-
return {
|
|
353
|
-
ok: false,
|
|
354
|
-
status: "error",
|
|
355
|
-
error: normalizeString(result.payload.error_description) ?? "GitHub device authorization failed."
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
const token = result.payload.access_token;
|
|
359
|
-
const user = await (input.fetchUser ?? fetchGitHubUserInfo)(token);
|
|
360
|
-
store.saveToken({
|
|
361
|
-
token,
|
|
362
|
-
tokenSource: "oauth-device",
|
|
363
|
-
login: user.login,
|
|
364
|
-
userId: user.id,
|
|
365
|
-
scopes: user.scopes ?? normalizeScopes(result.payload.scope),
|
|
366
|
-
selectedRepo: input.selectedRepo ?? undefined
|
|
367
|
-
});
|
|
368
|
-
store.clearPendingDevice(input.pollId);
|
|
369
|
-
return { ok: true, status: "signed-in", ...store.status({ oauthConfigured: true }) };
|
|
370
|
-
}
|
|
371
|
-
function checkGitHubRepoPermissions(input) {
|
|
372
|
-
const store = createGitHubAuthStore(input.projectRoot);
|
|
373
|
-
const auth = store.status({ oauthConfigured: input.oauthConfigured });
|
|
374
|
-
if (!auth.signedIn) {
|
|
375
|
-
return { ok: false, signedIn: false, canOpenPullRequest: false, reason: "not-authenticated" };
|
|
376
|
-
}
|
|
377
|
-
const normalizedScopes = auth.scopes.map((scope) => scope.toLowerCase());
|
|
378
|
-
const broadEnough = normalizedScopes.includes("repo") || normalizedScopes.includes("public_repo");
|
|
379
|
-
return {
|
|
380
|
-
ok: true,
|
|
381
|
-
signedIn: true,
|
|
382
|
-
login: auth.login,
|
|
383
|
-
scopes: auth.scopes,
|
|
384
|
-
canOpenPullRequest: broadEnough,
|
|
385
|
-
pullRequests: broadEnough,
|
|
386
|
-
push: broadEnough,
|
|
387
|
-
reason: broadEnough ? "stored-token" : "token-scope-unverified"
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
async function probeGitHubRepository(input) {
|
|
391
|
-
const headers = {
|
|
392
|
-
accept: "application/vnd.github+json",
|
|
393
|
-
"user-agent": "rig"
|
|
394
|
-
};
|
|
395
|
-
if (input.token) {
|
|
396
|
-
headers.authorization = `Bearer ${input.token}`;
|
|
397
|
-
}
|
|
398
|
-
try {
|
|
399
|
-
const response = await (input.fetchRepository ?? fetch)(`https://api.github.com/repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(input.repo)}`, { headers });
|
|
400
|
-
const payload = await response.json().catch(() => ({}));
|
|
401
|
-
if (response.ok) {
|
|
402
|
-
return {
|
|
403
|
-
ok: true,
|
|
404
|
-
owner: input.owner,
|
|
405
|
-
repo: input.repo,
|
|
406
|
-
status: response.status,
|
|
407
|
-
authenticated: Boolean(input.token),
|
|
408
|
-
authenticationRequired: payload.private === true && !input.token,
|
|
409
|
-
fullName: typeof payload.full_name === "string" ? payload.full_name : `${input.owner}/${input.repo}`,
|
|
410
|
-
private: typeof payload.private === "boolean" ? payload.private : null,
|
|
411
|
-
message: input.token ? "Repository access verified with signed-in GitHub credentials." : "Public repository access verified without credentials.",
|
|
412
|
-
scopes: input.scopes
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
const authenticationRequired = !input.token && (response.status === 401 || response.status === 403 || response.status === 404);
|
|
416
|
-
return {
|
|
417
|
-
ok: false,
|
|
418
|
-
owner: input.owner,
|
|
419
|
-
repo: input.repo,
|
|
420
|
-
status: response.status,
|
|
421
|
-
authenticated: Boolean(input.token),
|
|
422
|
-
authenticationRequired,
|
|
423
|
-
fullName: null,
|
|
424
|
-
private: null,
|
|
425
|
-
message: authenticationRequired ? "Repository is private, missing, or inaccessible without GitHub sign-in. Sign in before saving this config." : typeof payload.message === "string" ? payload.message : `GitHub repository probe failed (${response.status}).`,
|
|
426
|
-
scopes: input.scopes
|
|
427
|
-
};
|
|
428
|
-
} catch (error) {
|
|
429
|
-
return {
|
|
430
|
-
ok: false,
|
|
431
|
-
owner: input.owner,
|
|
432
|
-
repo: input.repo,
|
|
433
|
-
status: 0,
|
|
434
|
-
authenticated: Boolean(input.token),
|
|
435
|
-
authenticationRequired: !input.token,
|
|
436
|
-
fullName: null,
|
|
437
|
-
private: null,
|
|
438
|
-
message: error instanceof Error ? error.message : "GitHub repository probe failed.",
|
|
439
|
-
scopes: input.scopes
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
export {
|
|
444
|
-
saveGitHubTokenForProject,
|
|
445
|
-
resolveGitHubAuthStatus,
|
|
446
|
-
probeGitHubRepository,
|
|
447
|
-
pollGitHubDeviceFlow,
|
|
448
|
-
fetchGitHubUserInfo,
|
|
449
|
-
checkGitHubRepoPermissions,
|
|
450
|
-
beginGitHubDeviceFlow
|
|
451
|
-
};
|
package/dist/src/projects.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { GitHubProjectStatusField, GitHubProjectSummary } from "@rig/contracts";
|
|
2
|
-
export type { GitHubProjectStatusField, GitHubProjectSummary };
|
|
3
|
-
export type GitHubGraphQLFetch = (query: string, variables: Record<string, unknown>, token: string) => Promise<unknown>;
|
|
4
|
-
export declare function listGitHubProjects(input: {
|
|
5
|
-
owner: string;
|
|
6
|
-
token: string;
|
|
7
|
-
first?: number;
|
|
8
|
-
fetchGraphQL?: GitHubGraphQLFetch;
|
|
9
|
-
}): Promise<GitHubProjectSummary[]>;
|
|
10
|
-
export declare function resolveProjectStatusField(input: {
|
|
11
|
-
projectId: string;
|
|
12
|
-
token: string;
|
|
13
|
-
fetchGraphQL?: GitHubGraphQLFetch;
|
|
14
|
-
}): Promise<GitHubProjectStatusField>;
|
|
15
|
-
export declare function ensureIssueProjectItem(input: {
|
|
16
|
-
projectId: string;
|
|
17
|
-
issueNodeId: string;
|
|
18
|
-
token: string;
|
|
19
|
-
fetchGraphQL?: GitHubGraphQLFetch;
|
|
20
|
-
}): Promise<{
|
|
21
|
-
id: string;
|
|
22
|
-
created: boolean;
|
|
23
|
-
}>;
|
|
24
|
-
export declare function updateIssueProjectStatus(input: {
|
|
25
|
-
projectId: string;
|
|
26
|
-
itemId: string;
|
|
27
|
-
fieldId: string;
|
|
28
|
-
optionId: string;
|
|
29
|
-
token: string;
|
|
30
|
-
fetchGraphQL?: GitHubGraphQLFetch;
|
|
31
|
-
}): Promise<void>;
|