@morehao/dsh-github-trending 0.7.10 → 0.8.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/CHANGELOG.md +12 -0
- package/README.en.md +92 -0
- package/README.md +4 -0
- package/client.js +157 -21
- package/lib/{chunk-YJ5GF3MW.js → chunk-6PVBPJXO.js} +445 -13
- package/lib/index.d.ts +1 -0
- package/lib/index.js +9 -2
- package/lib/internal.d.ts +159 -8
- package/lib/internal.js +3 -1
- package/package.json +2 -1
|
@@ -205,8 +205,8 @@ function normalizeSettings(input) {
|
|
|
205
205
|
labels: strArray(pr.labels, DEFAULT_SETTINGS.pr.labels)
|
|
206
206
|
},
|
|
207
207
|
github: {
|
|
208
|
-
//
|
|
209
|
-
authMode: authMode === "pat"
|
|
208
|
+
// 白名单三值:'pat' / 'gh' / 'browser';其余(含旧版 'device')统一迁移到真正可用的「本机 gh」。
|
|
209
|
+
authMode: authMode === "pat" || authMode === "browser" ? authMode : "gh",
|
|
210
210
|
tokenRef: str(github.tokenRef, DEFAULT_SETTINGS.github.tokenRef).trim() || DEFAULT_SETTINGS.github.tokenRef
|
|
211
211
|
},
|
|
212
212
|
trending: {
|
|
@@ -784,6 +784,11 @@ var PLUGIN_CODES = [
|
|
|
784
784
|
"MODEL_NOT_FOUND",
|
|
785
785
|
"MODEL_PROVIDER_UNSUPPORTED",
|
|
786
786
|
"GITHUB_AUTH",
|
|
787
|
+
"GITHUB_AUTH_CANCELLED",
|
|
788
|
+
"GITHUB_AUTH_EXPIRED",
|
|
789
|
+
"GITHUB_AUTH_STATE",
|
|
790
|
+
"GITHUB_AUTH_EXCHANGE",
|
|
791
|
+
"GITHUB_AUTH_UNAVAILABLE",
|
|
787
792
|
"PUSH_FAILED",
|
|
788
793
|
"REPO_DIRTY",
|
|
789
794
|
"SUBSCRIBE_FAILED"
|
|
@@ -819,6 +824,11 @@ var RETRIABLE = {
|
|
|
819
824
|
MODEL_NOT_FOUND: false,
|
|
820
825
|
MODEL_PROVIDER_UNSUPPORTED: false,
|
|
821
826
|
GITHUB_AUTH: false,
|
|
827
|
+
GITHUB_AUTH_CANCELLED: false,
|
|
828
|
+
GITHUB_AUTH_EXPIRED: true,
|
|
829
|
+
GITHUB_AUTH_STATE: false,
|
|
830
|
+
GITHUB_AUTH_EXCHANGE: true,
|
|
831
|
+
GITHUB_AUTH_UNAVAILABLE: false,
|
|
822
832
|
PUSH_FAILED: true,
|
|
823
833
|
SUBSCRIBE_FAILED: true
|
|
824
834
|
};
|
|
@@ -829,7 +839,7 @@ function areaOf(code) {
|
|
|
829
839
|
if (code.startsWith("CORE_") || code === "IPC_INVALID") return "core";
|
|
830
840
|
if (code.startsWith("MODEL_")) return "model";
|
|
831
841
|
if (code.startsWith("REPO_") || code === "GIT_NOT_INSTALLED" || code === "GIT_OPERATION_FAILED") return "repo";
|
|
832
|
-
if (code === "GITHUB_AUTH") return "auth";
|
|
842
|
+
if (code === "GITHUB_AUTH" || code.startsWith("GITHUB_AUTH_")) return "auth";
|
|
833
843
|
return "run";
|
|
834
844
|
}
|
|
835
845
|
function reasonOf(code, message, details) {
|
|
@@ -899,6 +909,16 @@ function hintOf(code, message, reason) {
|
|
|
899
909
|
return "\u4ED3\u5E93\u6709\u672A\u63D0\u4EA4\u6539\u52A8\uFF1A\u63D2\u4EF6\u4E0D stash\u3001\u4E0D reset\uFF0C\u8BF7\u5148\u5904\u7406\u3002";
|
|
900
910
|
case "GITHUB_AUTH":
|
|
901
911
|
return "GitHub \u8BA4\u8BC1\u5931\u6548\uFF1A\u91CD\u65B0\u8FDE\u63A5\u6216\u66F4\u65B0 PAT\uFF1B\u672C\u5730 commit \u4F1A\u4FDD\u7559\uFF0C\u53EF\u76F4\u63A5\u91CD\u8BD5\u3002";
|
|
912
|
+
case "GITHUB_AUTH_CANCELLED":
|
|
913
|
+
return "\u5DF2\u53D6\u6D88 GitHub \u767B\u5F55\uFF1A\u9700\u8981\u65F6\u5728\u8BBE\u7F6E\u9875\u91CD\u65B0\u70B9\u300C\u7528 GitHub \u767B\u5F55\u300D\u3002";
|
|
914
|
+
case "GITHUB_AUTH_EXPIRED":
|
|
915
|
+
return "GitHub \u767B\u5F55\u8D85\u65F6\uFF1A\u91CD\u65B0\u53D1\u8D77\u767B\u5F55\u5373\u53EF\uFF08\u672A\u5199\u5165\u4EFB\u4F55\u51ED\u636E\uFF09\u3002";
|
|
916
|
+
case "GITHUB_AUTH_STATE":
|
|
917
|
+
return "GitHub \u56DE\u8C03\u6821\u9A8C\u5931\u8D25\uFF08state \u4E0D\u5339\u914D\u6216\u91CD\u590D\u56DE\u8C03\uFF09\uFF1A\u8FD9\u662F\u4E00\u6B21\u5F02\u5E38\u56DE\u8C03\uFF0C\u91CD\u65B0\u53D1\u8D77\u767B\u5F55\u3002";
|
|
918
|
+
case "GITHUB_AUTH_EXCHANGE":
|
|
919
|
+
return "\u7528\u6388\u6743\u7801\u6362\u53D6 GitHub token \u5931\u8D25\uFF1A\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5\uFF1B\u82E5\u6301\u7EED\u5931\u8D25\uFF0C\u786E\u8BA4 OAuth App \u7684 client_id \u4E0E\u56DE\u8C03\u5730\u5740\u914D\u7F6E\u4E00\u81F4\u3002";
|
|
920
|
+
case "GITHUB_AUTH_UNAVAILABLE":
|
|
921
|
+
return "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u6D4F\u89C8\u5668\u767B\u5F55\uFF08\u7F3A\u5C11 authorization \u670D\u52A1\uFF09\uFF1A\u6539\u7528\u300C\u672C\u673A gh\u300D\u6216 PAT \u65B9\u5F0F\u3002";
|
|
902
922
|
case "NOT_MERGEABLE":
|
|
903
923
|
return "PR \u65E0\u6CD5\u81EA\u52A8\u5408\u5E76\uFF08\u5E38\u89C1\u4E8E\u5206\u652F\u4FDD\u62A4\uFF09\uFF1APR \u5DF2\u4FDD\u7559\uFF0C\u9700\u4EBA\u5DE5\u5904\u7406\u3002";
|
|
904
924
|
case "PUSH_FAILED":
|
|
@@ -1911,11 +1931,13 @@ async function detectCoexistence(ownPath, options = {}) {
|
|
|
1911
1931
|
// src/host/credentials.ts
|
|
1912
1932
|
var credentials_exports = {};
|
|
1913
1933
|
__export(credentials_exports, {
|
|
1934
|
+
GITHUB_RECORD_KEY: () => GITHUB_RECORD_KEY,
|
|
1914
1935
|
credentialServices: () => credentialServices,
|
|
1915
1936
|
describeRef: () => describeRef,
|
|
1916
1937
|
resolveGithubToken: () => resolveGithubToken,
|
|
1917
1938
|
resolveRef: () => resolveRef
|
|
1918
1939
|
});
|
|
1940
|
+
var GITHUB_RECORD_KEY = "github/default";
|
|
1919
1941
|
function credentialServices(get) {
|
|
1920
1942
|
const service = get("credentials");
|
|
1921
1943
|
return service !== void 0 && typeof service?.resolve === "function" ? service : void 0;
|
|
@@ -1951,8 +1973,27 @@ async function resolveRef(services, ref, env = process.env) {
|
|
|
1951
1973
|
const envValue = fromEnv(ref, env);
|
|
1952
1974
|
return envValue === void 0 ? {} : { value: envValue, source: "env" };
|
|
1953
1975
|
}
|
|
1976
|
+
async function readGithubRecord(services) {
|
|
1977
|
+
if (typeof services?.readRecord !== "function") return void 0;
|
|
1978
|
+
try {
|
|
1979
|
+
return await services.readRecord(GITHUB_RECORD_KEY) ?? void 0;
|
|
1980
|
+
} catch {
|
|
1981
|
+
return void 0;
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
function grantToken(record) {
|
|
1985
|
+
if (record === void 0 || record.kind !== "grant") return void 0;
|
|
1986
|
+
const payload = record.payload;
|
|
1987
|
+
const token = payload?.token;
|
|
1988
|
+
return typeof token === "string" && token.trim() !== "" ? token : void 0;
|
|
1989
|
+
}
|
|
1954
1990
|
async function resolveGithubToken(services, authMode, tokenRef, deps = {}) {
|
|
1955
1991
|
const env = deps.env ?? process.env;
|
|
1992
|
+
if (authMode === "browser") {
|
|
1993
|
+
const record = await readGithubRecord(services);
|
|
1994
|
+
const token = grantToken(record);
|
|
1995
|
+
if (token !== void 0) return { token, source: "github-browser" };
|
|
1996
|
+
}
|
|
1956
1997
|
if (authMode === "gh") {
|
|
1957
1998
|
const execGh = deps.execGh ?? (() => defaultGhToken(deps.freshGh === true));
|
|
1958
1999
|
try {
|
|
@@ -3442,7 +3483,7 @@ async function probeGithubAuth(options) {
|
|
|
3442
3483
|
ok: false,
|
|
3443
3484
|
code: "GITHUB_AUTH",
|
|
3444
3485
|
source: resolved.source,
|
|
3445
|
-
message: options.authMode === "gh" ? "\u6CA1\u6709\u53D6\u5230\u672C\u673A gh \u7684\u767B\u5F55\u6001\uFF1A\u5148\u5728\u7EC8\u7AEF\u6267\u884C `gh auth login`\u3002" : `\u6CA1\u6709\u53D6\u5230\u51ED\u636E\u300C${options.tokenRef}\u300D\uFF1A\u8BF7\u5728\u73AF\u5883\u53D8\u91CF\u6216\u51ED\u636E\u91CC\u914D\u7F6E\u4E00\u4E2A Personal Access Token\u3002`
|
|
3486
|
+
message: options.authMode === "gh" ? "\u6CA1\u6709\u53D6\u5230\u672C\u673A gh \u7684\u767B\u5F55\u6001\uFF1A\u5148\u5728\u7EC8\u7AEF\u6267\u884C `gh auth login`\u3002" : options.authMode === "browser" ? "\u8FD8\u6CA1\u6709\u901A\u8FC7\u6D4F\u89C8\u5668\u767B\u5F55 GitHub\uFF1A\u5728\u8BBE\u7F6E\u9875\u70B9\u300C\u7528 GitHub \u767B\u5F55\u300D\u5B8C\u6210\u6388\u6743\u3002" : `\u6CA1\u6709\u53D6\u5230\u51ED\u636E\u300C${options.tokenRef}\u300D\uFF1A\u8BF7\u5728\u73AF\u5883\u53D8\u91CF\u6216\u51ED\u636E\u91CC\u914D\u7F6E\u4E00\u4E2A Personal Access Token\u3002`
|
|
3446
3487
|
};
|
|
3447
3488
|
}
|
|
3448
3489
|
const doFetch = options.fetchImpl ?? fetch;
|
|
@@ -3485,6 +3526,363 @@ async function probeGithubAuth(options) {
|
|
|
3485
3526
|
}
|
|
3486
3527
|
}
|
|
3487
3528
|
|
|
3529
|
+
// src/host/github-auth.ts
|
|
3530
|
+
var github_auth_exports = {};
|
|
3531
|
+
__export(github_auth_exports, {
|
|
3532
|
+
CALLBACK_PATH: () => CALLBACK_PATH,
|
|
3533
|
+
DEFAULT_SCOPE: () => DEFAULT_SCOPE,
|
|
3534
|
+
GITHUB_RECORD_KEY: () => GITHUB_RECORD_KEY2,
|
|
3535
|
+
attemptView: () => attemptView,
|
|
3536
|
+
beginGithubSignIn: () => beginGithubSignIn,
|
|
3537
|
+
buildAuthorizeUrl: () => buildAuthorizeUrl,
|
|
3538
|
+
cancelGithubSignIn: () => cancelGithubSignIn,
|
|
3539
|
+
createPkce: () => createPkce,
|
|
3540
|
+
registerGithubFlow: () => registerGithubFlow,
|
|
3541
|
+
resetAttemptForTest: () => resetAttemptForTest,
|
|
3542
|
+
verifyState: () => verifyState
|
|
3543
|
+
});
|
|
3544
|
+
import { createHash as createHash3, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
3545
|
+
var GITHUB_RECORD_KEY2 = "github/default";
|
|
3546
|
+
var CALLBACK_PATH = "/api/morehao/github-trending/oauth/github/callback";
|
|
3547
|
+
var AUTHORIZE_URL = "https://github.com/login/oauth/authorize";
|
|
3548
|
+
var ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
3549
|
+
var DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
3550
|
+
var GITHUB_USER_URL = "https://api.github.com/user";
|
|
3551
|
+
var DEFAULT_SCOPE = "repo";
|
|
3552
|
+
var DEFAULT_ATTEMPT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
3553
|
+
var current;
|
|
3554
|
+
function attemptView() {
|
|
3555
|
+
if (current === void 0) return { phase: "idle" };
|
|
3556
|
+
const { controller: _c, done: _d, ...view } = current;
|
|
3557
|
+
return view;
|
|
3558
|
+
}
|
|
3559
|
+
function base64url(buf) {
|
|
3560
|
+
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
3561
|
+
}
|
|
3562
|
+
function createPkce() {
|
|
3563
|
+
const verifier = base64url(randomBytes2(32));
|
|
3564
|
+
const challenge = base64url(createHash3("sha256").update(verifier).digest());
|
|
3565
|
+
return { verifier, challenge };
|
|
3566
|
+
}
|
|
3567
|
+
function verifyState(received, expected) {
|
|
3568
|
+
const a = Buffer.from(received);
|
|
3569
|
+
const b = Buffer.from(expected);
|
|
3570
|
+
return a.length === b.length && timingSafeEqual2(a, b);
|
|
3571
|
+
}
|
|
3572
|
+
function buildAuthorizeUrl(params) {
|
|
3573
|
+
const url = new URL(params.authorizeEndpoint);
|
|
3574
|
+
url.searchParams.set("client_id", params.clientId);
|
|
3575
|
+
url.searchParams.set("redirect_uri", params.redirectUri);
|
|
3576
|
+
url.searchParams.set("scope", params.scope);
|
|
3577
|
+
url.searchParams.set("state", params.state);
|
|
3578
|
+
url.searchParams.set("code_challenge", params.challenge);
|
|
3579
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
3580
|
+
return url.toString();
|
|
3581
|
+
}
|
|
3582
|
+
async function exchangeCode(fetchImpl, endpoint, params, signal) {
|
|
3583
|
+
const response = await fetchImpl(endpoint, {
|
|
3584
|
+
method: "POST",
|
|
3585
|
+
headers: { accept: "application/json", "content-type": "application/x-www-form-urlencoded" },
|
|
3586
|
+
body: new URLSearchParams({
|
|
3587
|
+
client_id: params.clientId,
|
|
3588
|
+
code: params.code,
|
|
3589
|
+
code_verifier: params.verifier,
|
|
3590
|
+
redirect_uri: params.redirectUri
|
|
3591
|
+
}).toString(),
|
|
3592
|
+
signal
|
|
3593
|
+
});
|
|
3594
|
+
if (!response.ok) throw new Error(`GitHub access_token HTTP ${response.status}`);
|
|
3595
|
+
const body = await response.json();
|
|
3596
|
+
if (typeof body.access_token !== "string" || body.access_token === "") {
|
|
3597
|
+
throw new Error(`GitHub access_token \u672A\u8FD4\u56DE token${typeof body.error === "string" ? `\uFF08${body.error}\uFF09` : ""}`);
|
|
3598
|
+
}
|
|
3599
|
+
return {
|
|
3600
|
+
token: body.access_token,
|
|
3601
|
+
...typeof body.token_type === "string" ? { tokenType: body.token_type } : {},
|
|
3602
|
+
...typeof body.scope === "string" ? { scope: body.scope } : {}
|
|
3603
|
+
};
|
|
3604
|
+
}
|
|
3605
|
+
async function fetchLogin(fetchImpl, endpoint, token, signal) {
|
|
3606
|
+
try {
|
|
3607
|
+
const response = await fetchImpl(endpoint, {
|
|
3608
|
+
headers: { authorization: `Bearer ${token}`, accept: "application/vnd.github+json", "user-agent": "dsh-github-trending" },
|
|
3609
|
+
signal
|
|
3610
|
+
});
|
|
3611
|
+
if (!response.ok) return void 0;
|
|
3612
|
+
const body = await response.json();
|
|
3613
|
+
return typeof body.login === "string" ? body.login : void 0;
|
|
3614
|
+
} catch {
|
|
3615
|
+
return void 0;
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
async function runBrowser(deps, session) {
|
|
3619
|
+
const webServer = deps.get("webServer");
|
|
3620
|
+
if (webServer === void 0 || typeof webServer.register !== "function") {
|
|
3621
|
+
throw new Error("webServer \u670D\u52A1\u4E0D\u53EF\u7528\uFF1A\u6D4F\u89C8\u5668\u767B\u5F55\u9700\u8981\u5B83\u627F\u8F7D OAuth \u56DE\u8C03");
|
|
3622
|
+
}
|
|
3623
|
+
const clientId = deps.clientId;
|
|
3624
|
+
if (clientId === void 0 || clientId === "") throw new Error("\u7F3A\u5C11 GitHub OAuth client_id");
|
|
3625
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
3626
|
+
const endpoints = normalizeEndpoints(deps.endpoints);
|
|
3627
|
+
const scope = deps.scope ?? DEFAULT_SCOPE;
|
|
3628
|
+
const state = base64url(randomBytes2(32));
|
|
3629
|
+
const { verifier, challenge } = createPkce();
|
|
3630
|
+
let resolveCode;
|
|
3631
|
+
let rejectCode;
|
|
3632
|
+
const codePromise = new Promise((resolve, reject) => {
|
|
3633
|
+
resolveCode = resolve;
|
|
3634
|
+
rejectCode = reject;
|
|
3635
|
+
});
|
|
3636
|
+
codePromise.catch(() => void 0);
|
|
3637
|
+
const dispose = webServer.register({
|
|
3638
|
+
kind: "exact",
|
|
3639
|
+
path: CALLBACK_PATH,
|
|
3640
|
+
handler: (req, res) => {
|
|
3641
|
+
let url;
|
|
3642
|
+
try {
|
|
3643
|
+
url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
3644
|
+
} catch {
|
|
3645
|
+
res.writeHead(400, { "cache-control": "no-store" }).end();
|
|
3646
|
+
return;
|
|
3647
|
+
}
|
|
3648
|
+
const code = url.searchParams.get("code") ?? "";
|
|
3649
|
+
const gotState = url.searchParams.get("state") ?? "";
|
|
3650
|
+
const single = url.searchParams.getAll("state").length === 1 && url.searchParams.getAll("code").length === 1;
|
|
3651
|
+
const bad = req.method !== "GET" || url.pathname !== CALLBACK_PATH || !single || code === "" || !verifyState(gotState, state);
|
|
3652
|
+
if (bad) {
|
|
3653
|
+
res.writeHead(400, { "cache-control": "no-store" }).end();
|
|
3654
|
+
return;
|
|
3655
|
+
}
|
|
3656
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" }).end('<!doctype html><meta charset="utf-8"><title>GitHub</title><p>\u5DF2\u5B8C\u6210\u767B\u5F55\uFF0C\u53EF\u4EE5\u5173\u95ED\u6B64\u9875\u9762\u3002</p>');
|
|
3657
|
+
resolveCode(code);
|
|
3658
|
+
}
|
|
3659
|
+
});
|
|
3660
|
+
try {
|
|
3661
|
+
const origin = localOrigin(webServer);
|
|
3662
|
+
const redirectUri = `${origin}${CALLBACK_PATH}`;
|
|
3663
|
+
const authorizeUrl = buildAuthorizeUrl({
|
|
3664
|
+
authorizeEndpoint: endpoints.authorize,
|
|
3665
|
+
clientId,
|
|
3666
|
+
redirectUri,
|
|
3667
|
+
scope,
|
|
3668
|
+
state,
|
|
3669
|
+
challenge
|
|
3670
|
+
});
|
|
3671
|
+
updateCurrent({ phase: "waiting-browser", authorizeUrl, expiresAt: Date.now() + attemptTimeout(deps) });
|
|
3672
|
+
session.notify?.({ message: "Continue in your browser to sign in to GitHub.", url: authorizeUrl });
|
|
3673
|
+
const code = await withAbort(codePromise, session.signal, () => rejectCode(new Error("authorization cancelled")));
|
|
3674
|
+
updateCurrent({ phase: "exchanging" });
|
|
3675
|
+
const exchanged = await exchangeCode(fetchImpl, endpoints.accessToken, { clientId, code, verifier, redirectUri }, session.signal);
|
|
3676
|
+
const login = await fetchLogin(fetchImpl, endpoints.user, exchanged.token, session.signal);
|
|
3677
|
+
const payload = {
|
|
3678
|
+
version: 1,
|
|
3679
|
+
token: exchanged.token,
|
|
3680
|
+
...login === void 0 ? {} : { login },
|
|
3681
|
+
tokenType: exchanged.tokenType ?? "bearer",
|
|
3682
|
+
scope: exchanged.scope ?? scope
|
|
3683
|
+
};
|
|
3684
|
+
if (typeof session.commit === "function") await session.commit({ kind: "grant", payload });
|
|
3685
|
+
updateCurrent({ phase: "done", ...login === void 0 ? {} : { login } });
|
|
3686
|
+
} finally {
|
|
3687
|
+
dispose();
|
|
3688
|
+
}
|
|
3689
|
+
}
|
|
3690
|
+
async function runDevice(deps, session) {
|
|
3691
|
+
const clientId = deps.clientId;
|
|
3692
|
+
if (clientId === void 0 || clientId === "") throw new Error("\u7F3A\u5C11 GitHub OAuth client_id");
|
|
3693
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
3694
|
+
const endpoints = normalizeEndpoints(deps.endpoints);
|
|
3695
|
+
const scope = deps.scope ?? DEFAULT_SCOPE;
|
|
3696
|
+
const startResponse = await fetchImpl(endpoints.deviceCode, {
|
|
3697
|
+
method: "POST",
|
|
3698
|
+
headers: { accept: "application/json", "content-type": "application/x-www-form-urlencoded" },
|
|
3699
|
+
body: new URLSearchParams({ client_id: clientId, scope }).toString(),
|
|
3700
|
+
signal: session.signal
|
|
3701
|
+
});
|
|
3702
|
+
if (!startResponse.ok) throw new Error(`GitHub device/code HTTP ${startResponse.status}`);
|
|
3703
|
+
const start = await startResponse.json();
|
|
3704
|
+
if (typeof start.device_code !== "string" || typeof start.user_code !== "string" || typeof start.verification_uri !== "string") {
|
|
3705
|
+
throw new Error("GitHub device/code \u8FD4\u56DE\u4E0D\u5B8C\u6574");
|
|
3706
|
+
}
|
|
3707
|
+
const deviceCode = start.device_code;
|
|
3708
|
+
const intervalSec = typeof start.interval === "number" && start.interval > 0 ? start.interval : 5;
|
|
3709
|
+
const expiresInSec = typeof start.expires_in === "number" && start.expires_in > 0 ? start.expires_in : 900;
|
|
3710
|
+
const deadline = Date.now() + expiresInSec * 1e3;
|
|
3711
|
+
updateCurrent({
|
|
3712
|
+
phase: "waiting-device",
|
|
3713
|
+
verificationUri: start.verification_uri,
|
|
3714
|
+
userCode: start.user_code,
|
|
3715
|
+
expiresAt: deadline
|
|
3716
|
+
});
|
|
3717
|
+
session.notify?.({
|
|
3718
|
+
message: "Enter this code on the verification page to finish signing in.",
|
|
3719
|
+
url: start.verification_uri,
|
|
3720
|
+
code: start.user_code
|
|
3721
|
+
});
|
|
3722
|
+
let waitMs = intervalSec * 1e3;
|
|
3723
|
+
while (Date.now() < deadline) {
|
|
3724
|
+
await sleep(waitMs, session.signal);
|
|
3725
|
+
const pollResponse = await fetchImpl(endpoints.accessToken, {
|
|
3726
|
+
method: "POST",
|
|
3727
|
+
headers: { accept: "application/json", "content-type": "application/x-www-form-urlencoded" },
|
|
3728
|
+
body: new URLSearchParams({
|
|
3729
|
+
client_id: clientId,
|
|
3730
|
+
device_code: deviceCode,
|
|
3731
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
3732
|
+
}).toString(),
|
|
3733
|
+
signal: session.signal
|
|
3734
|
+
});
|
|
3735
|
+
if (!pollResponse.ok) throw new Error(`GitHub access_token HTTP ${pollResponse.status}`);
|
|
3736
|
+
const poll = await pollResponse.json();
|
|
3737
|
+
if (typeof poll.access_token === "string" && poll.access_token !== "") {
|
|
3738
|
+
updateCurrent({ phase: "exchanging" });
|
|
3739
|
+
const login = await fetchLogin(fetchImpl, endpoints.user, poll.access_token, session.signal);
|
|
3740
|
+
const payload = {
|
|
3741
|
+
version: 1,
|
|
3742
|
+
token: poll.access_token,
|
|
3743
|
+
...login === void 0 ? {} : { login },
|
|
3744
|
+
tokenType: typeof poll.token_type === "string" ? poll.token_type : "bearer",
|
|
3745
|
+
scope: typeof poll.scope === "string" ? poll.scope : scope
|
|
3746
|
+
};
|
|
3747
|
+
if (typeof session.commit === "function") await session.commit({ kind: "grant", payload });
|
|
3748
|
+
updateCurrent({ phase: "done", ...login === void 0 ? {} : { login } });
|
|
3749
|
+
return;
|
|
3750
|
+
}
|
|
3751
|
+
if (poll.error === "authorization_pending") continue;
|
|
3752
|
+
if (poll.error === "slow_down") {
|
|
3753
|
+
waitMs += (typeof poll.interval === "number" && poll.interval > 0 ? poll.interval : 5) * 1e3;
|
|
3754
|
+
continue;
|
|
3755
|
+
}
|
|
3756
|
+
throw new Error(`GitHub device flow \u7EC8\u6B62\uFF1A${typeof poll.error === "string" ? poll.error : "unknown"}`);
|
|
3757
|
+
}
|
|
3758
|
+
throw new Error("GitHub device flow \u8D85\u65F6");
|
|
3759
|
+
}
|
|
3760
|
+
function registerGithubFlow(deps) {
|
|
3761
|
+
const authorization = deps.get("authorization");
|
|
3762
|
+
if (authorization === void 0 || typeof authorization.registerFlow !== "function") {
|
|
3763
|
+
deps.logger?.("auth.flow.skipped", { reason: "no-authorization-service" });
|
|
3764
|
+
return;
|
|
3765
|
+
}
|
|
3766
|
+
authorization.registerFlow({
|
|
3767
|
+
key: GITHUB_RECORD_KEY2,
|
|
3768
|
+
label: "GitHub",
|
|
3769
|
+
methods: [
|
|
3770
|
+
{ id: "browser", label: "Sign in with GitHub" },
|
|
3771
|
+
{ id: "device", label: "Use a device code" }
|
|
3772
|
+
],
|
|
3773
|
+
run: (session) => session.method === "device" ? runDevice(deps, session) : runBrowser(deps, session)
|
|
3774
|
+
});
|
|
3775
|
+
deps.logger?.("auth.flow.registered", { key: GITHUB_RECORD_KEY2 });
|
|
3776
|
+
}
|
|
3777
|
+
async function beginGithubSignIn(deps, method = "browser") {
|
|
3778
|
+
const authorization = deps.get("authorization");
|
|
3779
|
+
if (authorization === void 0 || typeof authorization.begin !== "function") {
|
|
3780
|
+
throw new Error("authorization \u670D\u52A1\u4E0D\u53EF\u7528\uFF1A\u65E0\u6CD5\u8FDB\u884C\u6D4F\u89C8\u5668\u767B\u5F55");
|
|
3781
|
+
}
|
|
3782
|
+
if (current !== void 0 && (current.phase === "waiting-browser" || current.phase === "waiting-device" || current.phase === "exchanging")) {
|
|
3783
|
+
throw new Error("already-in-flight");
|
|
3784
|
+
}
|
|
3785
|
+
const controller = new AbortController();
|
|
3786
|
+
const timer = setTimeout(() => controller.abort(), attemptTimeout(deps));
|
|
3787
|
+
const attempt = { phase: "idle", controller, done: Promise.resolve() };
|
|
3788
|
+
current = attempt;
|
|
3789
|
+
attempt.done = authorization.begin({
|
|
3790
|
+
key: GITHUB_RECORD_KEY2,
|
|
3791
|
+
method,
|
|
3792
|
+
signal: controller.signal,
|
|
3793
|
+
// 浏览器往返靠 authorizeUrl + 回调;notify 只更新内存视图,prompt 不支持(拒绝)。
|
|
3794
|
+
interaction: {
|
|
3795
|
+
notify: (notice) => {
|
|
3796
|
+
if (notice.code !== void 0 && notice.url !== void 0) {
|
|
3797
|
+
updateCurrent({ phase: "waiting-device", verificationUri: notice.url, userCode: notice.code });
|
|
3798
|
+
} else if (notice.url !== void 0) {
|
|
3799
|
+
updateCurrent({ phase: "waiting-browser", authorizeUrl: notice.url });
|
|
3800
|
+
}
|
|
3801
|
+
},
|
|
3802
|
+
prompt: () => Promise.reject(new Error("interactive prompt unsupported in this surface"))
|
|
3803
|
+
}
|
|
3804
|
+
}).then((outcome) => {
|
|
3805
|
+
if (outcome.status === "cancelled") updateCurrent({ phase: "idle" });
|
|
3806
|
+
}).catch((error) => {
|
|
3807
|
+
updateCurrent({ phase: "error", error: error instanceof Error ? error.message : String(error) });
|
|
3808
|
+
}).finally(() => clearTimeout(timer));
|
|
3809
|
+
await Promise.resolve();
|
|
3810
|
+
return attemptView();
|
|
3811
|
+
}
|
|
3812
|
+
function cancelGithubSignIn(deps) {
|
|
3813
|
+
const authorization = deps.get("authorization");
|
|
3814
|
+
if (authorization !== void 0 && typeof authorization.cancel === "function") {
|
|
3815
|
+
authorization.cancel(GITHUB_RECORD_KEY2);
|
|
3816
|
+
}
|
|
3817
|
+
if (current !== void 0) current.controller.abort();
|
|
3818
|
+
}
|
|
3819
|
+
function resetAttemptForTest() {
|
|
3820
|
+
current = void 0;
|
|
3821
|
+
}
|
|
3822
|
+
function normalizeEndpoints(endpoints = {}) {
|
|
3823
|
+
return {
|
|
3824
|
+
authorize: endpoints?.authorize ?? AUTHORIZE_URL,
|
|
3825
|
+
accessToken: endpoints?.accessToken ?? ACCESS_TOKEN_URL,
|
|
3826
|
+
deviceCode: endpoints?.deviceCode ?? DEVICE_CODE_URL,
|
|
3827
|
+
user: endpoints?.user ?? GITHUB_USER_URL
|
|
3828
|
+
};
|
|
3829
|
+
}
|
|
3830
|
+
function attemptTimeout(deps) {
|
|
3831
|
+
return typeof deps.attemptTimeoutMs === "number" && deps.attemptTimeoutMs > 0 ? deps.attemptTimeoutMs : DEFAULT_ATTEMPT_TIMEOUT_MS;
|
|
3832
|
+
}
|
|
3833
|
+
function localOrigin(webServer) {
|
|
3834
|
+
const port = webServer.port;
|
|
3835
|
+
if (typeof port !== "number" || port <= 0) throw new Error("webServer \u5C1A\u672A\u76D1\u542C\uFF0C\u65E0\u6CD5\u786E\u5B9A\u56DE\u8C03\u5730\u5740");
|
|
3836
|
+
const host = typeof webServer.host === "string" && webServer.host !== "" ? webServer.host : "127.0.0.1";
|
|
3837
|
+
const loopback = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
|
|
3838
|
+
return `http://${loopback}:${port}`;
|
|
3839
|
+
}
|
|
3840
|
+
function withAbort(promise, signal, onAbort) {
|
|
3841
|
+
if (signal === void 0) return promise;
|
|
3842
|
+
if (signal.aborted) {
|
|
3843
|
+
onAbort();
|
|
3844
|
+
return Promise.reject(new Error("authorization cancelled"));
|
|
3845
|
+
}
|
|
3846
|
+
return new Promise((resolve, reject) => {
|
|
3847
|
+
const abort = () => {
|
|
3848
|
+
onAbort();
|
|
3849
|
+
reject(new Error("authorization cancelled"));
|
|
3850
|
+
};
|
|
3851
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
3852
|
+
promise.then(
|
|
3853
|
+
(value) => {
|
|
3854
|
+
signal.removeEventListener("abort", abort);
|
|
3855
|
+
resolve(value);
|
|
3856
|
+
},
|
|
3857
|
+
(error) => {
|
|
3858
|
+
signal.removeEventListener("abort", abort);
|
|
3859
|
+
reject(error);
|
|
3860
|
+
}
|
|
3861
|
+
);
|
|
3862
|
+
});
|
|
3863
|
+
}
|
|
3864
|
+
function sleep(ms, signal) {
|
|
3865
|
+
return new Promise((resolve, reject) => {
|
|
3866
|
+
if (signal?.aborted === true) {
|
|
3867
|
+
reject(new Error("authorization cancelled"));
|
|
3868
|
+
return;
|
|
3869
|
+
}
|
|
3870
|
+
const timer = setTimeout(() => {
|
|
3871
|
+
signal?.removeEventListener("abort", abort);
|
|
3872
|
+
resolve();
|
|
3873
|
+
}, ms);
|
|
3874
|
+
const abort = () => {
|
|
3875
|
+
clearTimeout(timer);
|
|
3876
|
+
reject(new Error("authorization cancelled"));
|
|
3877
|
+
};
|
|
3878
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
3879
|
+
});
|
|
3880
|
+
}
|
|
3881
|
+
function updateCurrent(patch) {
|
|
3882
|
+
if (current === void 0) return;
|
|
3883
|
+
Object.assign(current, patch);
|
|
3884
|
+
}
|
|
3885
|
+
|
|
3488
3886
|
// src/host/import-cli.ts
|
|
3489
3887
|
var import_cli_exports = {};
|
|
3490
3888
|
__export(import_cli_exports, {
|
|
@@ -3532,7 +3930,7 @@ function scalar(raw) {
|
|
|
3532
3930
|
}
|
|
3533
3931
|
function parseSimpleYaml(text) {
|
|
3534
3932
|
const root = {};
|
|
3535
|
-
let
|
|
3933
|
+
let current2;
|
|
3536
3934
|
let currentIndent = -1;
|
|
3537
3935
|
for (const rawLine of text.split("\n")) {
|
|
3538
3936
|
const line = rawLine.replace(/\r$/, "");
|
|
@@ -3546,21 +3944,21 @@ function parseSimpleYaml(text) {
|
|
|
3546
3944
|
if (rest.trim() === "") {
|
|
3547
3945
|
const child = {};
|
|
3548
3946
|
root[key] = child;
|
|
3549
|
-
|
|
3947
|
+
current2 = child;
|
|
3550
3948
|
currentIndent = indent;
|
|
3551
3949
|
continue;
|
|
3552
3950
|
}
|
|
3553
3951
|
if (indent === 0) {
|
|
3554
3952
|
root[key] = scalar(rest);
|
|
3555
|
-
|
|
3953
|
+
current2 = void 0;
|
|
3556
3954
|
currentIndent = -1;
|
|
3557
3955
|
continue;
|
|
3558
3956
|
}
|
|
3559
|
-
if (
|
|
3957
|
+
if (current2 === void 0 || indent <= currentIndent) {
|
|
3560
3958
|
root[key] = scalar(rest);
|
|
3561
3959
|
continue;
|
|
3562
3960
|
}
|
|
3563
|
-
|
|
3961
|
+
current2[key] = scalar(rest);
|
|
3564
3962
|
}
|
|
3565
3963
|
return root;
|
|
3566
3964
|
}
|
|
@@ -3983,15 +4381,23 @@ import { PluginUpdateError } from "@morehao/dsh-plugin-kit/update";
|
|
|
3983
4381
|
var ROUTE_PREFIX = "/api/morehao/github-trending";
|
|
3984
4382
|
var HEADER = "x-dsh-github-trending";
|
|
3985
4383
|
var MAX_BODY2 = 512 * 1024;
|
|
4384
|
+
function githubAuthDeps(deps) {
|
|
4385
|
+
return {
|
|
4386
|
+
get: deps.get,
|
|
4387
|
+
...deps.fetchImpl === void 0 ? {} : { fetchImpl: deps.fetchImpl },
|
|
4388
|
+
...deps.githubClientId === void 0 ? {} : { clientId: deps.githubClientId },
|
|
4389
|
+
...deps.logger === void 0 ? {} : { logger: deps.logger }
|
|
4390
|
+
};
|
|
4391
|
+
}
|
|
3986
4392
|
function createRoutes(deps) {
|
|
3987
4393
|
const logger = deps.logger;
|
|
3988
|
-
let
|
|
4394
|
+
let current2 = { method: "GET", pathname: "/" };
|
|
3989
4395
|
const handler = createRouteHandler({
|
|
3990
4396
|
prefix: ROUTE_PREFIX,
|
|
3991
4397
|
header: HEADER,
|
|
3992
4398
|
maxBodyBytes: MAX_BODY2,
|
|
3993
4399
|
dispatch: async ({ method, pathname, url, body }) => {
|
|
3994
|
-
|
|
4400
|
+
current2 = { method, pathname };
|
|
3995
4401
|
return dispatch(method, pathname, url, body, deps);
|
|
3996
4402
|
},
|
|
3997
4403
|
// 错误码翻译留在插件侧(dispatcher 不做映射):保持与原实现的响应形状逐字一致。
|
|
@@ -4011,7 +4417,7 @@ function createRoutes(deps) {
|
|
|
4011
4417
|
}
|
|
4012
4418
|
const code = error instanceof IpcError ? error.code : "INTERNAL";
|
|
4013
4419
|
const message = redact(error instanceof Error ? error.message : String(error));
|
|
4014
|
-
logger?.("route.error", { pathname:
|
|
4420
|
+
logger?.("route.error", { pathname: current2.pathname, method: current2.method, code, message });
|
|
4015
4421
|
const status = code === "CONFIG_INVALID" ? (
|
|
4016
4422
|
// 「已有一次运行在进行中」是并发冲突而非参数错误:用 409,前端据此禁用按钮并提示。
|
|
4017
4423
|
message.includes("\u8FD0\u884C\u5728\u8FDB\u884C\u4E2D") ? 409 : 400
|
|
@@ -4165,6 +4571,26 @@ async function dispatch(method, pathname, url, body, deps) {
|
|
|
4165
4571
|
});
|
|
4166
4572
|
return { github: probe };
|
|
4167
4573
|
}
|
|
4574
|
+
if (method === "POST" && pathname === "/github/browser/begin") {
|
|
4575
|
+
const method2 = body.method === "device" ? "device" : "browser";
|
|
4576
|
+
try {
|
|
4577
|
+
const view = await beginGithubSignIn(githubAuthDeps(deps), method2);
|
|
4578
|
+
return { attempt: view };
|
|
4579
|
+
} catch (error) {
|
|
4580
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4581
|
+
if (message === "already-in-flight") {
|
|
4582
|
+
return { attempt: attemptView(), alreadyInFlight: true };
|
|
4583
|
+
}
|
|
4584
|
+
throw new IpcError("GITHUB_AUTH_UNAVAILABLE", redact(message));
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
if (method === "GET" && pathname === "/github/browser/status") {
|
|
4588
|
+
return { attempt: attemptView() };
|
|
4589
|
+
}
|
|
4590
|
+
if (method === "POST" && pathname === "/github/browser/cancel") {
|
|
4591
|
+
cancelGithubSignIn(githubAuthDeps(deps));
|
|
4592
|
+
return { attempt: attemptView() };
|
|
4593
|
+
}
|
|
4168
4594
|
if (method === "GET" && pathname === "/quickfill") {
|
|
4169
4595
|
const candidates = await listProviderCandidates({
|
|
4170
4596
|
llm: llmServices(deps.get),
|
|
@@ -4293,7 +4719,11 @@ async function statePayload(deps, settings) {
|
|
|
4293
4719
|
github: {
|
|
4294
4720
|
authMode: settings.github.authMode,
|
|
4295
4721
|
configured: github.token !== void 0,
|
|
4296
|
-
source: github.source
|
|
4722
|
+
source: github.source,
|
|
4723
|
+
/** 浏览器登录 attempt 视图(idle / waiting-browser / waiting-device / exchanging / done / error)。 */
|
|
4724
|
+
attempt: attemptView(),
|
|
4725
|
+
/** 是否配置了 browser 模式的 client_id(未配置时前端禁用「用 GitHub 登录」并给提示)。 */
|
|
4726
|
+
browserAvailable: deps.githubClientId !== void 0 && deps.githubClientId !== ""
|
|
4297
4727
|
}
|
|
4298
4728
|
};
|
|
4299
4729
|
}
|
|
@@ -4572,6 +5002,8 @@ export {
|
|
|
4572
5002
|
readPluginInfo,
|
|
4573
5003
|
plugin_info_exports,
|
|
4574
5004
|
github_exports,
|
|
5005
|
+
registerGithubFlow,
|
|
5006
|
+
github_auth_exports,
|
|
4575
5007
|
import_cli_exports,
|
|
4576
5008
|
pending_pr_exports,
|
|
4577
5009
|
preflight_exports,
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -7,8 +7,9 @@ import {
|
|
|
7
7
|
createTrendingHistoryTool,
|
|
8
8
|
createTrendingRunTool,
|
|
9
9
|
createUpdateService,
|
|
10
|
-
readPluginInfo
|
|
11
|
-
|
|
10
|
+
readPluginInfo,
|
|
11
|
+
registerGithubFlow
|
|
12
|
+
} from "./chunk-6PVBPJXO.js";
|
|
12
13
|
|
|
13
14
|
// src/index.ts
|
|
14
15
|
var name = "dsh-github-trending";
|
|
@@ -63,6 +64,11 @@ function apply(ctx, config = {}) {
|
|
|
63
64
|
onError: (error) => ctx.logger?.warn?.(`[dsh-github-trending] \u5B9A\u65F6\u8FD0\u884C\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`)
|
|
64
65
|
});
|
|
65
66
|
const pluginInfo = readPluginInfo();
|
|
67
|
+
registerGithubFlow({
|
|
68
|
+
get,
|
|
69
|
+
logger: log,
|
|
70
|
+
...config.githubClientId === void 0 ? {} : { clientId: config.githubClientId }
|
|
71
|
+
});
|
|
66
72
|
const update = createUpdateService({
|
|
67
73
|
get,
|
|
68
74
|
env,
|
|
@@ -83,6 +89,7 @@ function apply(ctx, config = {}) {
|
|
|
83
89
|
version: VERSION,
|
|
84
90
|
pluginName: pluginInfo.name,
|
|
85
91
|
...pluginInfo.repository === void 0 ? {} : { repository: pluginInfo.repository },
|
|
92
|
+
...config.githubClientId === void 0 ? {} : { githubClientId: config.githubClientId },
|
|
86
93
|
logger: log
|
|
87
94
|
});
|
|
88
95
|
const register = () => {
|