@morehao/dsh-github-trending 0.7.11 → 0.8.1
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 +10 -0
- package/client.js +123 -4
- package/lib/{chunk-YJ5GF3MW.js → chunk-LLTW4YHJ.js} +456 -13
- package/lib/index.d.ts +1 -0
- package/lib/index.js +9 -2
- package/lib/internal.d.ts +172 -8
- package/lib/internal.js +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,6 +55,16 @@ GitHub 趋势榜的 **DeepSeek Harness 插件**:按日 / 周 / 月周期定时
|
|
|
55
55
|
|
|
56
56
|

|
|
57
57
|
|
|
58
|
+
### GitHub 认证
|
|
59
|
+
|
|
60
|
+
认证分区支持三种方式(可随时切换):
|
|
61
|
+
|
|
62
|
+
- **浏览器登录(默认推荐)**:点「用 GitHub 登录」在浏览器完成授权,无需手工粘贴 token。走 OAuth PKCE + 本机回环回调(或设备码),token 由 DSH 凭据记录安全保存,不落 `settings.json`。插件内置了公开的 OAuth App client_id(client_id 属公开标识、非机密;纯 PKCE 不使用 client_secret),开箱即用。
|
|
63
|
+
- **本机 gh**:复用你终端里 `gh auth login` 的登录态。
|
|
64
|
+
- **PAT**:手工填 Personal Access Token(存到凭据引用 / 环境变量)。
|
|
65
|
+
|
|
66
|
+
> 浏览器登录需要核心提供 `authorization` 服务;若当前核心版本不含该能力,「浏览器登录」会提示不可用并引导改用 gh / PAT。
|
|
67
|
+
|
|
58
68
|
### 订阅
|
|
59
69
|
|
|
60
70
|
(可选)订阅一组仓库,每周把每个仓库当周新增的 Release 合成一段中文摘要,与榜单同分支、同 PR 一起提交;订阅列表随数据仓版本化,可审计、可复现。
|
package/client.js
CHANGED
|
@@ -123,7 +123,19 @@ var DICT = {
|
|
|
123
123
|
"auth.mode": "方式",
|
|
124
124
|
"auth.pat": "PAT",
|
|
125
125
|
"auth.gh": "本机 gh",
|
|
126
|
+
"auth.browser": "浏览器登录",
|
|
126
127
|
"auth.gh.hint": "使用本机 gh 登录态:终端执行 `gh auth login` 完成登录,再点「测试认证」。",
|
|
128
|
+
"auth.browser.hint": "在浏览器里完成 GitHub 授权,token 由 DSH 安全保存,无需手工粘贴。",
|
|
129
|
+
"auth.browser.signin": "用 GitHub 登录",
|
|
130
|
+
"auth.browser.waiting": "等待浏览器授权…",
|
|
131
|
+
"auth.browser.code": "在打开的页面输入验证码:",
|
|
132
|
+
"auth.browser.cancel": "取消",
|
|
133
|
+
"auth.browser.done": "已登录",
|
|
134
|
+
"auth.browser.device": "用设备码登录",
|
|
135
|
+
"auth.browser.unavailable": "当前环境未配置 GitHub OAuth client_id,浏览器登录不可用:请改用「本机 gh」或 PAT。",
|
|
136
|
+
"auth.browser.openHint": "若浏览器未自动打开,请手动打开发起授权,或复制链接:",
|
|
137
|
+
"auth.browser.copy": "复制链接",
|
|
138
|
+
"auth.browser.copied": "授权链接已复制",
|
|
127
139
|
"auth.pat.hint": "在 GitHub → Settings → Developer settings 生成 Personal Access Token(勾选 public_repo),填到下方引用或环境变量。",
|
|
128
140
|
"auth.tokenRef": "凭据引用",
|
|
129
141
|
"auth.tokenRef.hint": "凭据引用名 / 环境变量名",
|
|
@@ -652,7 +664,19 @@ var DICT = {
|
|
|
652
664
|
"auth.mode": "Mode",
|
|
653
665
|
"auth.pat": "PAT",
|
|
654
666
|
"auth.gh": "Local gh",
|
|
667
|
+
"auth.browser": "Browser login",
|
|
655
668
|
"auth.gh.hint": "Uses your local gh login: run `gh auth login` in a terminal, then click Test authentication.",
|
|
669
|
+
"auth.browser.hint": "Authorize GitHub in your browser; DSH stores the token securely, no manual paste.",
|
|
670
|
+
"auth.browser.signin": "Sign in with GitHub",
|
|
671
|
+
"auth.browser.waiting": "Waiting for browser authorization…",
|
|
672
|
+
"auth.browser.code": "Enter this code on the opened page:",
|
|
673
|
+
"auth.browser.cancel": "Cancel",
|
|
674
|
+
"auth.browser.done": "Signed in",
|
|
675
|
+
"auth.browser.device": "Use a device code",
|
|
676
|
+
"auth.browser.unavailable": "No GitHub OAuth client_id configured in this environment, so browser sign-in is unavailable: use Local gh or PAT instead.",
|
|
677
|
+
"auth.browser.openHint": "If the browser did not open, open the authorization page manually, or copy the link:",
|
|
678
|
+
"auth.browser.copy": "Copy link",
|
|
679
|
+
"auth.browser.copied": "Authorization link copied",
|
|
656
680
|
"auth.pat.hint": "Create a Personal Access Token under GitHub → Settings → Developer settings (scope public_repo) and put it in the ref below or an env var.",
|
|
657
681
|
"auth.tokenRef": "Credential ref",
|
|
658
682
|
"auth.tokenRef.hint": "credential ref / env var name",
|
|
@@ -1851,6 +1875,23 @@ async function apiCall(method, path, body, options = {}) {
|
|
|
1851
1875
|
}
|
|
1852
1876
|
return json;
|
|
1853
1877
|
}
|
|
1878
|
+
async function beginGithubSignIn(method = "browser") {
|
|
1879
|
+
const payload = await apiCall("POST", "/github/browser/begin", { method });
|
|
1880
|
+
return payload.attempt;
|
|
1881
|
+
}
|
|
1882
|
+
async function getGithubSignInStatus(signal) {
|
|
1883
|
+
const payload = await apiCall(
|
|
1884
|
+
"GET",
|
|
1885
|
+
"/github/browser/status",
|
|
1886
|
+
void 0,
|
|
1887
|
+
signal === void 0 ? {} : { signal }
|
|
1888
|
+
);
|
|
1889
|
+
return payload.attempt;
|
|
1890
|
+
}
|
|
1891
|
+
async function cancelGithubSignIn() {
|
|
1892
|
+
const payload = await apiCall("POST", "/github/browser/cancel", {});
|
|
1893
|
+
return payload.attempt;
|
|
1894
|
+
}
|
|
1854
1895
|
function releaseRunsOf(state) {
|
|
1855
1896
|
if (state === null || state === void 0) return [];
|
|
1856
1897
|
const root = Array.isArray(state.releaseRuns) ? state.releaseRuns : void 0;
|
|
@@ -2078,6 +2119,7 @@ function SettingsView(props) {
|
|
|
2078
2119
|
const [branchLoading, setBranchLoading] = import_react.default.useState(false);
|
|
2079
2120
|
const [testing, setTesting] = import_react.default.useState(false);
|
|
2080
2121
|
const [githubTest, setGithubTest] = import_react.default.useState(null);
|
|
2122
|
+
const [signIn, setSignIn] = import_react.default.useState({ phase: "idle" });
|
|
2081
2123
|
const anyDirty = SECTIONS.some((id) => dirtySections[id]);
|
|
2082
2124
|
const sectionEditable = section !== "source";
|
|
2083
2125
|
import_react.default.useEffect(() => {
|
|
@@ -2205,6 +2247,52 @@ function SettingsView(props) {
|
|
|
2205
2247
|
setTesting(false);
|
|
2206
2248
|
}
|
|
2207
2249
|
};
|
|
2250
|
+
const startSignIn = async (method) => {
|
|
2251
|
+
try {
|
|
2252
|
+
const view = await beginGithubSignIn(method);
|
|
2253
|
+
setSignIn(view);
|
|
2254
|
+
if (view.phase === "waiting-browser" && view.authorizeUrl !== void 0) {
|
|
2255
|
+
try {
|
|
2256
|
+
window.open(view.authorizeUrl, "_blank", "noopener,noreferrer");
|
|
2257
|
+
} catch {
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
} catch (error) {
|
|
2261
|
+
notify("err", error instanceof Error ? error.message : String(error));
|
|
2262
|
+
}
|
|
2263
|
+
};
|
|
2264
|
+
const cancelSignIn = async () => {
|
|
2265
|
+
try {
|
|
2266
|
+
setSignIn(await cancelGithubSignIn());
|
|
2267
|
+
} catch (error) {
|
|
2268
|
+
notify("err", error instanceof Error ? error.message : String(error));
|
|
2269
|
+
}
|
|
2270
|
+
};
|
|
2271
|
+
const copyAuthorizeLink = () => {
|
|
2272
|
+
if (signIn.authorizeUrl === void 0) return;
|
|
2273
|
+
void navigator.clipboard?.writeText(signIn.authorizeUrl).then(() => notify("ok", t("auth.browser.copied")));
|
|
2274
|
+
};
|
|
2275
|
+
const signInActive = signIn.phase === "waiting-browser" || signIn.phase === "waiting-device" || signIn.phase === "exchanging";
|
|
2276
|
+
import_react.default.useEffect(() => {
|
|
2277
|
+
if (!signInActive) return void 0;
|
|
2278
|
+
let alive = true;
|
|
2279
|
+
const timer = setInterval(() => {
|
|
2280
|
+
void getGithubSignInStatus().then((view) => {
|
|
2281
|
+
if (!alive) return;
|
|
2282
|
+
setSignIn(view);
|
|
2283
|
+
if (view.phase === "done") {
|
|
2284
|
+
setGithubTest(view.login === void 0 ? { ok: true } : { ok: true, login: view.login });
|
|
2285
|
+
notify("ok", t("auth.browser.done"));
|
|
2286
|
+
void store.refresh({ quiet: true });
|
|
2287
|
+
}
|
|
2288
|
+
}).catch(() => {
|
|
2289
|
+
});
|
|
2290
|
+
}, 1500);
|
|
2291
|
+
return () => {
|
|
2292
|
+
alive = false;
|
|
2293
|
+
clearInterval(timer);
|
|
2294
|
+
};
|
|
2295
|
+
}, [signInActive]);
|
|
2208
2296
|
const importCli = async () => {
|
|
2209
2297
|
try {
|
|
2210
2298
|
const result = await apiCall(
|
|
@@ -2243,6 +2331,8 @@ function SettingsView(props) {
|
|
|
2243
2331
|
const branchText = branchLoading ? t("branch.loading") : branchInfo?.branch !== void 0 ? branchInfo.branch : draft.repo.baseBranch !== "" ? draft.repo.baseBranch : "—";
|
|
2244
2332
|
const branchHint = branchInfo?.source === "origin-head" ? t("branch.fromOrigin") : branchInfo?.source === "head" ? t("branch.fromHead") : branchInfo?.message !== void 0 ? branchInfo.message : t("branch.readonlyHint");
|
|
2245
2333
|
const isGh = draft.github.authMode === "gh";
|
|
2334
|
+
const isBrowser = draft.github.authMode === "browser";
|
|
2335
|
+
const browserAvailable = state.github.browserAvailable === true;
|
|
2246
2336
|
const sections = [
|
|
2247
2337
|
{ id: "source", label: t("st.source") },
|
|
2248
2338
|
{ id: "model", label: t("st.model") },
|
|
@@ -2419,16 +2509,32 @@ function SettingsView(props) {
|
|
|
2419
2509
|
value: draft.github.authMode,
|
|
2420
2510
|
options: [
|
|
2421
2511
|
{ value: "gh", label: t("auth.gh") },
|
|
2422
|
-
{ value: "pat", label: t("auth.pat") }
|
|
2512
|
+
{ value: "pat", label: t("auth.pat") },
|
|
2513
|
+
{ value: "browser", label: t("auth.browser") }
|
|
2423
2514
|
],
|
|
2424
2515
|
onChange: (value) => update((next) => {
|
|
2425
|
-
next.github.authMode = value === "pat" ? "pat" : "gh";
|
|
2516
|
+
next.github.authMode = value === "pat" ? "pat" : value === "browser" ? "browser" : "gh";
|
|
2426
2517
|
})
|
|
2427
2518
|
}),
|
|
2428
2519
|
h(Pill, { kind: state.github.configured ? "ok" : "warn" }, state.github.configured ? `${t("auth.configured")} · ${state.github.source}` : t("auth.missing"))
|
|
2429
2520
|
),
|
|
2430
|
-
h("p", { className: "dshgt-hint" }, isGh ? t("auth.gh.hint") : t("auth.pat.hint")),
|
|
2431
|
-
isGh ? null : h(
|
|
2521
|
+
h("p", { className: "dshgt-hint" }, isGh ? t("auth.gh.hint") : isBrowser ? t("auth.browser.hint") : t("auth.pat.hint")),
|
|
2522
|
+
isGh ? null : isBrowser ? h(
|
|
2523
|
+
Row,
|
|
2524
|
+
{ label: t("auth.status") },
|
|
2525
|
+
browserAvailable ? h(
|
|
2526
|
+
Btn,
|
|
2527
|
+
{
|
|
2528
|
+
small: true,
|
|
2529
|
+
variant: "primary",
|
|
2530
|
+
onClick: () => void startSignIn("browser"),
|
|
2531
|
+
disabled: signInActive
|
|
2532
|
+
},
|
|
2533
|
+
signIn.phase === "done" && signIn.login !== void 0 ? `${t("auth.browser.done")} · @${signIn.login}` : signInActive ? t("auth.browser.waiting") : t("auth.browser.signin")
|
|
2534
|
+
) : h("span", { className: "dshgt-hint" }, t("auth.browser.unavailable")),
|
|
2535
|
+
browserAvailable ? h(Btn, { small: true, variant: "ghost", onClick: () => void startSignIn("device"), disabled: signInActive }, t("auth.browser.device")) : null,
|
|
2536
|
+
signInActive ? h(Btn, { small: true, variant: "ghost", onClick: () => void cancelSignIn() }, t("auth.browser.cancel")) : null
|
|
2537
|
+
) : h(
|
|
2432
2538
|
Row,
|
|
2433
2539
|
{ label: t("auth.tokenRef") },
|
|
2434
2540
|
h(Input, { mono: true, width: 220, value: draft.github.tokenRef, placeholder: "GITHUB_TOKEN", onInput: (value) => update((next) => {
|
|
@@ -2436,6 +2542,19 @@ function SettingsView(props) {
|
|
|
2436
2542
|
}) }),
|
|
2437
2543
|
h("span", { className: "dshgt-hint" }, t("auth.tokenRef.hint"))
|
|
2438
2544
|
),
|
|
2545
|
+
// 浏览器登录进行中的状态行:授权页链接(可复制)+ 设备码。
|
|
2546
|
+
isBrowser && signIn.phase !== "idle" && signIn.phase !== "done" ? h(
|
|
2547
|
+
"div",
|
|
2548
|
+
{ className: "dshgt-hint" },
|
|
2549
|
+
signIn.authorizeUrl !== void 0 ? h(
|
|
2550
|
+
"div",
|
|
2551
|
+
{ className: "dshgt-desc" },
|
|
2552
|
+
h("span", null, t("auth.browser.openHint")),
|
|
2553
|
+
" ",
|
|
2554
|
+
h(Btn, { small: true, variant: "ghost", onClick: () => copyAuthorizeLink() }, t("auth.browser.copy"))
|
|
2555
|
+
) : null,
|
|
2556
|
+
signIn.userCode !== void 0 ? h("div", { className: "dshgt-mono" }, `${t("auth.browser.code")} ${signIn.userCode}`) : null
|
|
2557
|
+
) : null,
|
|
2439
2558
|
h(
|
|
2440
2559
|
Row,
|
|
2441
2560
|
{ label: t("auth.test") },
|
|
@@ -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,370 @@ 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_GITHUB_CLIENT_ID: () => DEFAULT_GITHUB_CLIENT_ID,
|
|
3534
|
+
DEFAULT_SCOPE: () => DEFAULT_SCOPE,
|
|
3535
|
+
GITHUB_RECORD_KEY: () => GITHUB_RECORD_KEY2,
|
|
3536
|
+
attemptView: () => attemptView,
|
|
3537
|
+
beginGithubSignIn: () => beginGithubSignIn,
|
|
3538
|
+
buildAuthorizeUrl: () => buildAuthorizeUrl,
|
|
3539
|
+
cancelGithubSignIn: () => cancelGithubSignIn,
|
|
3540
|
+
createPkce: () => createPkce,
|
|
3541
|
+
registerGithubFlow: () => registerGithubFlow,
|
|
3542
|
+
resetAttemptForTest: () => resetAttemptForTest,
|
|
3543
|
+
resolveClientId: () => resolveClientId,
|
|
3544
|
+
verifyState: () => verifyState
|
|
3545
|
+
});
|
|
3546
|
+
import { createHash as createHash3, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
3547
|
+
var GITHUB_RECORD_KEY2 = "github/default";
|
|
3548
|
+
var CALLBACK_PATH = "/api/morehao/github-trending/oauth/github/callback";
|
|
3549
|
+
var DEFAULT_GITHUB_CLIENT_ID = "Ov23li7rhp0EKoqbkJKY";
|
|
3550
|
+
function resolveClientId(deps) {
|
|
3551
|
+
const fromConfig = deps.clientId;
|
|
3552
|
+
return fromConfig !== void 0 && fromConfig !== "" ? fromConfig : DEFAULT_GITHUB_CLIENT_ID;
|
|
3553
|
+
}
|
|
3554
|
+
var AUTHORIZE_URL = "https://github.com/login/oauth/authorize";
|
|
3555
|
+
var ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
3556
|
+
var DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
3557
|
+
var GITHUB_USER_URL = "https://api.github.com/user";
|
|
3558
|
+
var DEFAULT_SCOPE = "repo";
|
|
3559
|
+
var DEFAULT_ATTEMPT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
3560
|
+
var current;
|
|
3561
|
+
function attemptView() {
|
|
3562
|
+
if (current === void 0) return { phase: "idle" };
|
|
3563
|
+
const { controller: _c, done: _d, ...view } = current;
|
|
3564
|
+
return view;
|
|
3565
|
+
}
|
|
3566
|
+
function base64url(buf) {
|
|
3567
|
+
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
3568
|
+
}
|
|
3569
|
+
function createPkce() {
|
|
3570
|
+
const verifier = base64url(randomBytes2(32));
|
|
3571
|
+
const challenge = base64url(createHash3("sha256").update(verifier).digest());
|
|
3572
|
+
return { verifier, challenge };
|
|
3573
|
+
}
|
|
3574
|
+
function verifyState(received, expected) {
|
|
3575
|
+
const a = Buffer.from(received);
|
|
3576
|
+
const b = Buffer.from(expected);
|
|
3577
|
+
return a.length === b.length && timingSafeEqual2(a, b);
|
|
3578
|
+
}
|
|
3579
|
+
function buildAuthorizeUrl(params) {
|
|
3580
|
+
const url = new URL(params.authorizeEndpoint);
|
|
3581
|
+
url.searchParams.set("client_id", params.clientId);
|
|
3582
|
+
url.searchParams.set("redirect_uri", params.redirectUri);
|
|
3583
|
+
url.searchParams.set("scope", params.scope);
|
|
3584
|
+
url.searchParams.set("state", params.state);
|
|
3585
|
+
url.searchParams.set("code_challenge", params.challenge);
|
|
3586
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
3587
|
+
return url.toString();
|
|
3588
|
+
}
|
|
3589
|
+
async function exchangeCode(fetchImpl, endpoint, params, signal) {
|
|
3590
|
+
const response = await fetchImpl(endpoint, {
|
|
3591
|
+
method: "POST",
|
|
3592
|
+
headers: { accept: "application/json", "content-type": "application/x-www-form-urlencoded" },
|
|
3593
|
+
body: new URLSearchParams({
|
|
3594
|
+
client_id: params.clientId,
|
|
3595
|
+
code: params.code,
|
|
3596
|
+
code_verifier: params.verifier,
|
|
3597
|
+
redirect_uri: params.redirectUri
|
|
3598
|
+
}).toString(),
|
|
3599
|
+
signal
|
|
3600
|
+
});
|
|
3601
|
+
if (!response.ok) throw new Error(`GitHub access_token HTTP ${response.status}`);
|
|
3602
|
+
const body = await response.json();
|
|
3603
|
+
if (typeof body.access_token !== "string" || body.access_token === "") {
|
|
3604
|
+
throw new Error(`GitHub access_token \u672A\u8FD4\u56DE token${typeof body.error === "string" ? `\uFF08${body.error}\uFF09` : ""}`);
|
|
3605
|
+
}
|
|
3606
|
+
return {
|
|
3607
|
+
token: body.access_token,
|
|
3608
|
+
...typeof body.token_type === "string" ? { tokenType: body.token_type } : {},
|
|
3609
|
+
...typeof body.scope === "string" ? { scope: body.scope } : {}
|
|
3610
|
+
};
|
|
3611
|
+
}
|
|
3612
|
+
async function fetchLogin(fetchImpl, endpoint, token, signal) {
|
|
3613
|
+
try {
|
|
3614
|
+
const response = await fetchImpl(endpoint, {
|
|
3615
|
+
headers: { authorization: `Bearer ${token}`, accept: "application/vnd.github+json", "user-agent": "dsh-github-trending" },
|
|
3616
|
+
signal
|
|
3617
|
+
});
|
|
3618
|
+
if (!response.ok) return void 0;
|
|
3619
|
+
const body = await response.json();
|
|
3620
|
+
return typeof body.login === "string" ? body.login : void 0;
|
|
3621
|
+
} catch {
|
|
3622
|
+
return void 0;
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
async function runBrowser(deps, session) {
|
|
3626
|
+
const webServer = deps.get("webServer");
|
|
3627
|
+
if (webServer === void 0 || typeof webServer.register !== "function") {
|
|
3628
|
+
throw new Error("webServer \u670D\u52A1\u4E0D\u53EF\u7528\uFF1A\u6D4F\u89C8\u5668\u767B\u5F55\u9700\u8981\u5B83\u627F\u8F7D OAuth \u56DE\u8C03");
|
|
3629
|
+
}
|
|
3630
|
+
const clientId = resolveClientId(deps);
|
|
3631
|
+
if (clientId === void 0 || clientId === "") throw new Error("\u7F3A\u5C11 GitHub OAuth client_id");
|
|
3632
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
3633
|
+
const endpoints = normalizeEndpoints(deps.endpoints);
|
|
3634
|
+
const scope = deps.scope ?? DEFAULT_SCOPE;
|
|
3635
|
+
const state = base64url(randomBytes2(32));
|
|
3636
|
+
const { verifier, challenge } = createPkce();
|
|
3637
|
+
let resolveCode;
|
|
3638
|
+
let rejectCode;
|
|
3639
|
+
const codePromise = new Promise((resolve, reject) => {
|
|
3640
|
+
resolveCode = resolve;
|
|
3641
|
+
rejectCode = reject;
|
|
3642
|
+
});
|
|
3643
|
+
codePromise.catch(() => void 0);
|
|
3644
|
+
const dispose = webServer.register({
|
|
3645
|
+
kind: "exact",
|
|
3646
|
+
path: CALLBACK_PATH,
|
|
3647
|
+
handler: (req, res) => {
|
|
3648
|
+
let url;
|
|
3649
|
+
try {
|
|
3650
|
+
url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
3651
|
+
} catch {
|
|
3652
|
+
res.writeHead(400, { "cache-control": "no-store" }).end();
|
|
3653
|
+
return;
|
|
3654
|
+
}
|
|
3655
|
+
const code = url.searchParams.get("code") ?? "";
|
|
3656
|
+
const gotState = url.searchParams.get("state") ?? "";
|
|
3657
|
+
const single = url.searchParams.getAll("state").length === 1 && url.searchParams.getAll("code").length === 1;
|
|
3658
|
+
const bad = req.method !== "GET" || url.pathname !== CALLBACK_PATH || !single || code === "" || !verifyState(gotState, state);
|
|
3659
|
+
if (bad) {
|
|
3660
|
+
res.writeHead(400, { "cache-control": "no-store" }).end();
|
|
3661
|
+
return;
|
|
3662
|
+
}
|
|
3663
|
+
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>');
|
|
3664
|
+
resolveCode(code);
|
|
3665
|
+
}
|
|
3666
|
+
});
|
|
3667
|
+
try {
|
|
3668
|
+
const origin = localOrigin(webServer);
|
|
3669
|
+
const redirectUri = `${origin}${CALLBACK_PATH}`;
|
|
3670
|
+
const authorizeUrl = buildAuthorizeUrl({
|
|
3671
|
+
authorizeEndpoint: endpoints.authorize,
|
|
3672
|
+
clientId,
|
|
3673
|
+
redirectUri,
|
|
3674
|
+
scope,
|
|
3675
|
+
state,
|
|
3676
|
+
challenge
|
|
3677
|
+
});
|
|
3678
|
+
updateCurrent({ phase: "waiting-browser", authorizeUrl, expiresAt: Date.now() + attemptTimeout(deps) });
|
|
3679
|
+
session.notify?.({ message: "Continue in your browser to sign in to GitHub.", url: authorizeUrl });
|
|
3680
|
+
const code = await withAbort(codePromise, session.signal, () => rejectCode(new Error("authorization cancelled")));
|
|
3681
|
+
updateCurrent({ phase: "exchanging" });
|
|
3682
|
+
const exchanged = await exchangeCode(fetchImpl, endpoints.accessToken, { clientId, code, verifier, redirectUri }, session.signal);
|
|
3683
|
+
const login = await fetchLogin(fetchImpl, endpoints.user, exchanged.token, session.signal);
|
|
3684
|
+
const payload = {
|
|
3685
|
+
version: 1,
|
|
3686
|
+
token: exchanged.token,
|
|
3687
|
+
...login === void 0 ? {} : { login },
|
|
3688
|
+
tokenType: exchanged.tokenType ?? "bearer",
|
|
3689
|
+
scope: exchanged.scope ?? scope
|
|
3690
|
+
};
|
|
3691
|
+
if (typeof session.commit === "function") await session.commit({ kind: "grant", payload });
|
|
3692
|
+
updateCurrent({ phase: "done", ...login === void 0 ? {} : { login } });
|
|
3693
|
+
} finally {
|
|
3694
|
+
dispose();
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
async function runDevice(deps, session) {
|
|
3698
|
+
const clientId = resolveClientId(deps);
|
|
3699
|
+
if (clientId === void 0 || clientId === "") throw new Error("\u7F3A\u5C11 GitHub OAuth client_id");
|
|
3700
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
3701
|
+
const endpoints = normalizeEndpoints(deps.endpoints);
|
|
3702
|
+
const scope = deps.scope ?? DEFAULT_SCOPE;
|
|
3703
|
+
const startResponse = await fetchImpl(endpoints.deviceCode, {
|
|
3704
|
+
method: "POST",
|
|
3705
|
+
headers: { accept: "application/json", "content-type": "application/x-www-form-urlencoded" },
|
|
3706
|
+
body: new URLSearchParams({ client_id: clientId, scope }).toString(),
|
|
3707
|
+
signal: session.signal
|
|
3708
|
+
});
|
|
3709
|
+
if (!startResponse.ok) throw new Error(`GitHub device/code HTTP ${startResponse.status}`);
|
|
3710
|
+
const start = await startResponse.json();
|
|
3711
|
+
if (typeof start.device_code !== "string" || typeof start.user_code !== "string" || typeof start.verification_uri !== "string") {
|
|
3712
|
+
throw new Error("GitHub device/code \u8FD4\u56DE\u4E0D\u5B8C\u6574");
|
|
3713
|
+
}
|
|
3714
|
+
const deviceCode = start.device_code;
|
|
3715
|
+
const intervalSec = typeof start.interval === "number" && start.interval > 0 ? start.interval : 5;
|
|
3716
|
+
const expiresInSec = typeof start.expires_in === "number" && start.expires_in > 0 ? start.expires_in : 900;
|
|
3717
|
+
const deadline = Date.now() + expiresInSec * 1e3;
|
|
3718
|
+
updateCurrent({
|
|
3719
|
+
phase: "waiting-device",
|
|
3720
|
+
verificationUri: start.verification_uri,
|
|
3721
|
+
userCode: start.user_code,
|
|
3722
|
+
expiresAt: deadline
|
|
3723
|
+
});
|
|
3724
|
+
session.notify?.({
|
|
3725
|
+
message: "Enter this code on the verification page to finish signing in.",
|
|
3726
|
+
url: start.verification_uri,
|
|
3727
|
+
code: start.user_code
|
|
3728
|
+
});
|
|
3729
|
+
let waitMs = intervalSec * 1e3;
|
|
3730
|
+
while (Date.now() < deadline) {
|
|
3731
|
+
await sleep(waitMs, session.signal);
|
|
3732
|
+
const pollResponse = await fetchImpl(endpoints.accessToken, {
|
|
3733
|
+
method: "POST",
|
|
3734
|
+
headers: { accept: "application/json", "content-type": "application/x-www-form-urlencoded" },
|
|
3735
|
+
body: new URLSearchParams({
|
|
3736
|
+
client_id: clientId,
|
|
3737
|
+
device_code: deviceCode,
|
|
3738
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
3739
|
+
}).toString(),
|
|
3740
|
+
signal: session.signal
|
|
3741
|
+
});
|
|
3742
|
+
if (!pollResponse.ok) throw new Error(`GitHub access_token HTTP ${pollResponse.status}`);
|
|
3743
|
+
const poll = await pollResponse.json();
|
|
3744
|
+
if (typeof poll.access_token === "string" && poll.access_token !== "") {
|
|
3745
|
+
updateCurrent({ phase: "exchanging" });
|
|
3746
|
+
const login = await fetchLogin(fetchImpl, endpoints.user, poll.access_token, session.signal);
|
|
3747
|
+
const payload = {
|
|
3748
|
+
version: 1,
|
|
3749
|
+
token: poll.access_token,
|
|
3750
|
+
...login === void 0 ? {} : { login },
|
|
3751
|
+
tokenType: typeof poll.token_type === "string" ? poll.token_type : "bearer",
|
|
3752
|
+
scope: typeof poll.scope === "string" ? poll.scope : scope
|
|
3753
|
+
};
|
|
3754
|
+
if (typeof session.commit === "function") await session.commit({ kind: "grant", payload });
|
|
3755
|
+
updateCurrent({ phase: "done", ...login === void 0 ? {} : { login } });
|
|
3756
|
+
return;
|
|
3757
|
+
}
|
|
3758
|
+
if (poll.error === "authorization_pending") continue;
|
|
3759
|
+
if (poll.error === "slow_down") {
|
|
3760
|
+
waitMs += (typeof poll.interval === "number" && poll.interval > 0 ? poll.interval : 5) * 1e3;
|
|
3761
|
+
continue;
|
|
3762
|
+
}
|
|
3763
|
+
throw new Error(`GitHub device flow \u7EC8\u6B62\uFF1A${typeof poll.error === "string" ? poll.error : "unknown"}`);
|
|
3764
|
+
}
|
|
3765
|
+
throw new Error("GitHub device flow \u8D85\u65F6");
|
|
3766
|
+
}
|
|
3767
|
+
function registerGithubFlow(deps) {
|
|
3768
|
+
const authorization = deps.get("authorization");
|
|
3769
|
+
if (authorization === void 0 || typeof authorization.registerFlow !== "function") {
|
|
3770
|
+
deps.logger?.("auth.flow.skipped", { reason: "no-authorization-service" });
|
|
3771
|
+
return;
|
|
3772
|
+
}
|
|
3773
|
+
authorization.registerFlow({
|
|
3774
|
+
key: GITHUB_RECORD_KEY2,
|
|
3775
|
+
label: "GitHub",
|
|
3776
|
+
methods: [
|
|
3777
|
+
{ id: "browser", label: "Sign in with GitHub" },
|
|
3778
|
+
{ id: "device", label: "Use a device code" }
|
|
3779
|
+
],
|
|
3780
|
+
run: (session) => session.method === "device" ? runDevice(deps, session) : runBrowser(deps, session)
|
|
3781
|
+
});
|
|
3782
|
+
deps.logger?.("auth.flow.registered", { key: GITHUB_RECORD_KEY2 });
|
|
3783
|
+
}
|
|
3784
|
+
async function beginGithubSignIn(deps, method = "browser") {
|
|
3785
|
+
const authorization = deps.get("authorization");
|
|
3786
|
+
if (authorization === void 0 || typeof authorization.begin !== "function") {
|
|
3787
|
+
throw new Error("authorization \u670D\u52A1\u4E0D\u53EF\u7528\uFF1A\u65E0\u6CD5\u8FDB\u884C\u6D4F\u89C8\u5668\u767B\u5F55");
|
|
3788
|
+
}
|
|
3789
|
+
if (current !== void 0 && (current.phase === "waiting-browser" || current.phase === "waiting-device" || current.phase === "exchanging")) {
|
|
3790
|
+
throw new Error("already-in-flight");
|
|
3791
|
+
}
|
|
3792
|
+
const controller = new AbortController();
|
|
3793
|
+
const timer = setTimeout(() => controller.abort(), attemptTimeout(deps));
|
|
3794
|
+
const attempt = { phase: "idle", controller, done: Promise.resolve() };
|
|
3795
|
+
current = attempt;
|
|
3796
|
+
attempt.done = authorization.begin({
|
|
3797
|
+
key: GITHUB_RECORD_KEY2,
|
|
3798
|
+
method,
|
|
3799
|
+
signal: controller.signal,
|
|
3800
|
+
// 浏览器往返靠 authorizeUrl + 回调;notify 只更新内存视图,prompt 不支持(拒绝)。
|
|
3801
|
+
interaction: {
|
|
3802
|
+
notify: (notice) => {
|
|
3803
|
+
if (notice.code !== void 0 && notice.url !== void 0) {
|
|
3804
|
+
updateCurrent({ phase: "waiting-device", verificationUri: notice.url, userCode: notice.code });
|
|
3805
|
+
} else if (notice.url !== void 0) {
|
|
3806
|
+
updateCurrent({ phase: "waiting-browser", authorizeUrl: notice.url });
|
|
3807
|
+
}
|
|
3808
|
+
},
|
|
3809
|
+
prompt: () => Promise.reject(new Error("interactive prompt unsupported in this surface"))
|
|
3810
|
+
}
|
|
3811
|
+
}).then((outcome) => {
|
|
3812
|
+
if (outcome.status === "cancelled") updateCurrent({ phase: "idle" });
|
|
3813
|
+
}).catch((error) => {
|
|
3814
|
+
updateCurrent({ phase: "error", error: error instanceof Error ? error.message : String(error) });
|
|
3815
|
+
}).finally(() => clearTimeout(timer));
|
|
3816
|
+
await Promise.resolve();
|
|
3817
|
+
return attemptView();
|
|
3818
|
+
}
|
|
3819
|
+
function cancelGithubSignIn(deps) {
|
|
3820
|
+
const authorization = deps.get("authorization");
|
|
3821
|
+
if (authorization !== void 0 && typeof authorization.cancel === "function") {
|
|
3822
|
+
authorization.cancel(GITHUB_RECORD_KEY2);
|
|
3823
|
+
}
|
|
3824
|
+
if (current !== void 0) current.controller.abort();
|
|
3825
|
+
}
|
|
3826
|
+
function resetAttemptForTest() {
|
|
3827
|
+
current = void 0;
|
|
3828
|
+
}
|
|
3829
|
+
function normalizeEndpoints(endpoints = {}) {
|
|
3830
|
+
return {
|
|
3831
|
+
authorize: endpoints?.authorize ?? AUTHORIZE_URL,
|
|
3832
|
+
accessToken: endpoints?.accessToken ?? ACCESS_TOKEN_URL,
|
|
3833
|
+
deviceCode: endpoints?.deviceCode ?? DEVICE_CODE_URL,
|
|
3834
|
+
user: endpoints?.user ?? GITHUB_USER_URL
|
|
3835
|
+
};
|
|
3836
|
+
}
|
|
3837
|
+
function attemptTimeout(deps) {
|
|
3838
|
+
return typeof deps.attemptTimeoutMs === "number" && deps.attemptTimeoutMs > 0 ? deps.attemptTimeoutMs : DEFAULT_ATTEMPT_TIMEOUT_MS;
|
|
3839
|
+
}
|
|
3840
|
+
function localOrigin(webServer) {
|
|
3841
|
+
const port = webServer.port;
|
|
3842
|
+
if (typeof port !== "number" || port <= 0) throw new Error("webServer \u5C1A\u672A\u76D1\u542C\uFF0C\u65E0\u6CD5\u786E\u5B9A\u56DE\u8C03\u5730\u5740");
|
|
3843
|
+
const host = typeof webServer.host === "string" && webServer.host !== "" ? webServer.host : "127.0.0.1";
|
|
3844
|
+
const loopback = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
|
|
3845
|
+
return `http://${loopback}:${port}`;
|
|
3846
|
+
}
|
|
3847
|
+
function withAbort(promise, signal, onAbort) {
|
|
3848
|
+
if (signal === void 0) return promise;
|
|
3849
|
+
if (signal.aborted) {
|
|
3850
|
+
onAbort();
|
|
3851
|
+
return Promise.reject(new Error("authorization cancelled"));
|
|
3852
|
+
}
|
|
3853
|
+
return new Promise((resolve, reject) => {
|
|
3854
|
+
const abort = () => {
|
|
3855
|
+
onAbort();
|
|
3856
|
+
reject(new Error("authorization cancelled"));
|
|
3857
|
+
};
|
|
3858
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
3859
|
+
promise.then(
|
|
3860
|
+
(value) => {
|
|
3861
|
+
signal.removeEventListener("abort", abort);
|
|
3862
|
+
resolve(value);
|
|
3863
|
+
},
|
|
3864
|
+
(error) => {
|
|
3865
|
+
signal.removeEventListener("abort", abort);
|
|
3866
|
+
reject(error);
|
|
3867
|
+
}
|
|
3868
|
+
);
|
|
3869
|
+
});
|
|
3870
|
+
}
|
|
3871
|
+
function sleep(ms, signal) {
|
|
3872
|
+
return new Promise((resolve, reject) => {
|
|
3873
|
+
if (signal?.aborted === true) {
|
|
3874
|
+
reject(new Error("authorization cancelled"));
|
|
3875
|
+
return;
|
|
3876
|
+
}
|
|
3877
|
+
const timer = setTimeout(() => {
|
|
3878
|
+
signal?.removeEventListener("abort", abort);
|
|
3879
|
+
resolve();
|
|
3880
|
+
}, ms);
|
|
3881
|
+
const abort = () => {
|
|
3882
|
+
clearTimeout(timer);
|
|
3883
|
+
reject(new Error("authorization cancelled"));
|
|
3884
|
+
};
|
|
3885
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
3886
|
+
});
|
|
3887
|
+
}
|
|
3888
|
+
function updateCurrent(patch) {
|
|
3889
|
+
if (current === void 0) return;
|
|
3890
|
+
Object.assign(current, patch);
|
|
3891
|
+
}
|
|
3892
|
+
|
|
3488
3893
|
// src/host/import-cli.ts
|
|
3489
3894
|
var import_cli_exports = {};
|
|
3490
3895
|
__export(import_cli_exports, {
|
|
@@ -3532,7 +3937,7 @@ function scalar(raw) {
|
|
|
3532
3937
|
}
|
|
3533
3938
|
function parseSimpleYaml(text) {
|
|
3534
3939
|
const root = {};
|
|
3535
|
-
let
|
|
3940
|
+
let current2;
|
|
3536
3941
|
let currentIndent = -1;
|
|
3537
3942
|
for (const rawLine of text.split("\n")) {
|
|
3538
3943
|
const line = rawLine.replace(/\r$/, "");
|
|
@@ -3546,21 +3951,21 @@ function parseSimpleYaml(text) {
|
|
|
3546
3951
|
if (rest.trim() === "") {
|
|
3547
3952
|
const child = {};
|
|
3548
3953
|
root[key] = child;
|
|
3549
|
-
|
|
3954
|
+
current2 = child;
|
|
3550
3955
|
currentIndent = indent;
|
|
3551
3956
|
continue;
|
|
3552
3957
|
}
|
|
3553
3958
|
if (indent === 0) {
|
|
3554
3959
|
root[key] = scalar(rest);
|
|
3555
|
-
|
|
3960
|
+
current2 = void 0;
|
|
3556
3961
|
currentIndent = -1;
|
|
3557
3962
|
continue;
|
|
3558
3963
|
}
|
|
3559
|
-
if (
|
|
3964
|
+
if (current2 === void 0 || indent <= currentIndent) {
|
|
3560
3965
|
root[key] = scalar(rest);
|
|
3561
3966
|
continue;
|
|
3562
3967
|
}
|
|
3563
|
-
|
|
3968
|
+
current2[key] = scalar(rest);
|
|
3564
3969
|
}
|
|
3565
3970
|
return root;
|
|
3566
3971
|
}
|
|
@@ -3983,15 +4388,27 @@ import { PluginUpdateError } from "@morehao/dsh-plugin-kit/update";
|
|
|
3983
4388
|
var ROUTE_PREFIX = "/api/morehao/github-trending";
|
|
3984
4389
|
var HEADER = "x-dsh-github-trending";
|
|
3985
4390
|
var MAX_BODY2 = 512 * 1024;
|
|
4391
|
+
function isAuthorizationAvailable(get) {
|
|
4392
|
+
const authorization = get("authorization");
|
|
4393
|
+
return authorization !== void 0 && typeof authorization.begin === "function";
|
|
4394
|
+
}
|
|
4395
|
+
function githubAuthDeps(deps) {
|
|
4396
|
+
return {
|
|
4397
|
+
get: deps.get,
|
|
4398
|
+
...deps.fetchImpl === void 0 ? {} : { fetchImpl: deps.fetchImpl },
|
|
4399
|
+
...deps.githubClientId === void 0 ? {} : { clientId: deps.githubClientId },
|
|
4400
|
+
...deps.logger === void 0 ? {} : { logger: deps.logger }
|
|
4401
|
+
};
|
|
4402
|
+
}
|
|
3986
4403
|
function createRoutes(deps) {
|
|
3987
4404
|
const logger = deps.logger;
|
|
3988
|
-
let
|
|
4405
|
+
let current2 = { method: "GET", pathname: "/" };
|
|
3989
4406
|
const handler = createRouteHandler({
|
|
3990
4407
|
prefix: ROUTE_PREFIX,
|
|
3991
4408
|
header: HEADER,
|
|
3992
4409
|
maxBodyBytes: MAX_BODY2,
|
|
3993
4410
|
dispatch: async ({ method, pathname, url, body }) => {
|
|
3994
|
-
|
|
4411
|
+
current2 = { method, pathname };
|
|
3995
4412
|
return dispatch(method, pathname, url, body, deps);
|
|
3996
4413
|
},
|
|
3997
4414
|
// 错误码翻译留在插件侧(dispatcher 不做映射):保持与原实现的响应形状逐字一致。
|
|
@@ -4011,7 +4428,7 @@ function createRoutes(deps) {
|
|
|
4011
4428
|
}
|
|
4012
4429
|
const code = error instanceof IpcError ? error.code : "INTERNAL";
|
|
4013
4430
|
const message = redact(error instanceof Error ? error.message : String(error));
|
|
4014
|
-
logger?.("route.error", { pathname:
|
|
4431
|
+
logger?.("route.error", { pathname: current2.pathname, method: current2.method, code, message });
|
|
4015
4432
|
const status = code === "CONFIG_INVALID" ? (
|
|
4016
4433
|
// 「已有一次运行在进行中」是并发冲突而非参数错误:用 409,前端据此禁用按钮并提示。
|
|
4017
4434
|
message.includes("\u8FD0\u884C\u5728\u8FDB\u884C\u4E2D") ? 409 : 400
|
|
@@ -4165,6 +4582,26 @@ async function dispatch(method, pathname, url, body, deps) {
|
|
|
4165
4582
|
});
|
|
4166
4583
|
return { github: probe };
|
|
4167
4584
|
}
|
|
4585
|
+
if (method === "POST" && pathname === "/github/browser/begin") {
|
|
4586
|
+
const method2 = body.method === "device" ? "device" : "browser";
|
|
4587
|
+
try {
|
|
4588
|
+
const view = await beginGithubSignIn(githubAuthDeps(deps), method2);
|
|
4589
|
+
return { attempt: view };
|
|
4590
|
+
} catch (error) {
|
|
4591
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4592
|
+
if (message === "already-in-flight") {
|
|
4593
|
+
return { attempt: attemptView(), alreadyInFlight: true };
|
|
4594
|
+
}
|
|
4595
|
+
throw new IpcError("GITHUB_AUTH_UNAVAILABLE", redact(message));
|
|
4596
|
+
}
|
|
4597
|
+
}
|
|
4598
|
+
if (method === "GET" && pathname === "/github/browser/status") {
|
|
4599
|
+
return { attempt: attemptView() };
|
|
4600
|
+
}
|
|
4601
|
+
if (method === "POST" && pathname === "/github/browser/cancel") {
|
|
4602
|
+
cancelGithubSignIn(githubAuthDeps(deps));
|
|
4603
|
+
return { attempt: attemptView() };
|
|
4604
|
+
}
|
|
4168
4605
|
if (method === "GET" && pathname === "/quickfill") {
|
|
4169
4606
|
const candidates = await listProviderCandidates({
|
|
4170
4607
|
llm: llmServices(deps.get),
|
|
@@ -4293,7 +4730,11 @@ async function statePayload(deps, settings) {
|
|
|
4293
4730
|
github: {
|
|
4294
4731
|
authMode: settings.github.authMode,
|
|
4295
4732
|
configured: github.token !== void 0,
|
|
4296
|
-
source: github.source
|
|
4733
|
+
source: github.source,
|
|
4734
|
+
/** 浏览器登录 attempt 视图(idle / waiting-browser / waiting-device / exchanging / done / error)。 */
|
|
4735
|
+
attempt: attemptView(),
|
|
4736
|
+
/** 浏览器登录是否可用:核心提供了 authorization 服务即可(client_id 有内置默认)。 */
|
|
4737
|
+
browserAvailable: isAuthorizationAvailable(deps.get)
|
|
4297
4738
|
}
|
|
4298
4739
|
};
|
|
4299
4740
|
}
|
|
@@ -4572,6 +5013,8 @@ export {
|
|
|
4572
5013
|
readPluginInfo,
|
|
4573
5014
|
plugin_info_exports,
|
|
4574
5015
|
github_exports,
|
|
5016
|
+
registerGithubFlow,
|
|
5017
|
+
github_auth_exports,
|
|
4575
5018
|
import_cli_exports,
|
|
4576
5019
|
pending_pr_exports,
|
|
4577
5020
|
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-LLTW4YHJ.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 = () => {
|
package/lib/internal.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { Updater, PluginMeta, Logger } from '@morehao/dsh-plugin-kit/update';
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
/** 插件在核心之外新增的码。前缀统一避免与核心码冲突。 */
|
|
20
|
-
declare const PLUGIN_CODES: readonly ["CORE_UNAVAILABLE", "CORE_INCOMPATIBLE", "CORE_SPAWN_FAILED", "CORE_TIMEOUT", "CORE_CANCELLED", "CORE_CONFIG_CHANNEL_FAILED", "CORE_NOT_DOWNLOADED", "CORE_CHECKSUM_MISMATCH", "IPC_INVALID", "MODEL_KEY_MISSING", "MODEL_NOT_FOUND", "MODEL_PROVIDER_UNSUPPORTED", "GITHUB_AUTH", "PUSH_FAILED", "REPO_DIRTY", "SUBSCRIBE_FAILED"];
|
|
20
|
+
declare const PLUGIN_CODES: readonly ["CORE_UNAVAILABLE", "CORE_INCOMPATIBLE", "CORE_SPAWN_FAILED", "CORE_TIMEOUT", "CORE_CANCELLED", "CORE_CONFIG_CHANNEL_FAILED", "CORE_NOT_DOWNLOADED", "CORE_CHECKSUM_MISMATCH", "IPC_INVALID", "MODEL_KEY_MISSING", "MODEL_NOT_FOUND", "MODEL_PROVIDER_UNSUPPORTED", "GITHUB_AUTH", "GITHUB_AUTH_CANCELLED", "GITHUB_AUTH_EXPIRED", "GITHUB_AUTH_STATE", "GITHUB_AUTH_EXCHANGE", "GITHUB_AUTH_UNAVAILABLE", "PUSH_FAILED", "REPO_DIRTY", "SUBSCRIBE_FAILED"];
|
|
21
21
|
type PluginCode = (typeof PLUGIN_CODES)[number];
|
|
22
22
|
type AnyCode = ErrorCode | PluginCode;
|
|
23
23
|
interface ClassifiedError {
|
|
@@ -211,8 +211,11 @@ declare namespace ipc {
|
|
|
211
211
|
|
|
212
212
|
type Provider = string;
|
|
213
213
|
type CommitMode = 'branch' | 'direct';
|
|
214
|
-
/**
|
|
215
|
-
|
|
214
|
+
/**
|
|
215
|
+
* 三种可用方式:本机 `gh` 登录态、手动 PAT、浏览器 GitHub 登录(读核心凭据记录)。
|
|
216
|
+
* 历史的 `device` 在归一化时迁移为 `gh`;`browser` 无记录时会降级到 ref/env 兜底。
|
|
217
|
+
*/
|
|
218
|
+
type AuthMode = 'pat' | 'gh' | 'browser';
|
|
216
219
|
/**
|
|
217
220
|
* 榜单周期(core 0.4.0 的 `--period` 三取值)。
|
|
218
221
|
* `trending.since` 复用同一枚举:单次运行「抓取窗口 = 标注周期」由核心保证
|
|
@@ -1084,16 +1087,27 @@ interface ResolvedSecret {
|
|
|
1084
1087
|
value?: string;
|
|
1085
1088
|
source?: string;
|
|
1086
1089
|
}
|
|
1090
|
+
interface CredentialRecord {
|
|
1091
|
+
kind: string;
|
|
1092
|
+
payload: unknown;
|
|
1093
|
+
}
|
|
1087
1094
|
interface CredentialServices {
|
|
1088
1095
|
describe?: (ref: string) => Promise<CredentialDescription> | CredentialDescription;
|
|
1089
1096
|
resolve?: (ref: string) => Promise<ResolvedSecret | undefined | null> | ResolvedSecret | undefined | null;
|
|
1090
1097
|
set?: (ref: string, value: string) => Promise<unknown> | unknown;
|
|
1098
|
+
/**
|
|
1099
|
+
* record 半边(`<scope>/<id>` 键):只读,用于 `authMode=browser` 读 GitHub 登录记录。
|
|
1100
|
+
* 由核心 `dsh-credentials-local` 的同一对象提供(与 ref 半边并存);缺失时 browser 模式退回 ref/env。
|
|
1101
|
+
*/
|
|
1102
|
+
readRecord?: (key: string) => Promise<CredentialRecord | undefined> | CredentialRecord | undefined;
|
|
1091
1103
|
}
|
|
1104
|
+
/** 浏览器登录写入核心凭据记录的键(与核心 `@deepseek-ai/dsh-github-auth` 提案同名同构)。 */
|
|
1105
|
+
declare const GITHUB_RECORD_KEY$1 = "github/default";
|
|
1092
1106
|
declare function credentialServices(get: (name: string) => unknown): CredentialServices | undefined;
|
|
1093
1107
|
declare function describeRef(services: CredentialServices | undefined, ref: string, env?: NodeJS.ProcessEnv): Promise<CredentialDescription>;
|
|
1094
1108
|
declare function resolveRef(services: CredentialServices | undefined, ref: string, env?: NodeJS.ProcessEnv): Promise<ResolvedSecret>;
|
|
1095
|
-
/** GitHub token:按 authMode 取,`gh`
|
|
1096
|
-
declare function resolveGithubToken(services: CredentialServices | undefined, authMode: 'pat' | 'gh', tokenRef: string, deps?: {
|
|
1109
|
+
/** GitHub token:按 authMode 取,`gh` 读本机 gh 登录态,`browser` 读核心凭据记录,`pat` 读 ref/env。 */
|
|
1110
|
+
declare function resolveGithubToken(services: CredentialServices | undefined, authMode: 'pat' | 'gh' | 'browser', tokenRef: string, deps?: {
|
|
1097
1111
|
env?: NodeJS.ProcessEnv;
|
|
1098
1112
|
execGh?: () => Promise<string | undefined>;
|
|
1099
1113
|
freshGh?: boolean;
|
|
@@ -1103,6 +1117,7 @@ declare function resolveGithubToken(services: CredentialServices | undefined, au
|
|
|
1103
1117
|
}>;
|
|
1104
1118
|
|
|
1105
1119
|
type credentials_CredentialDescription = CredentialDescription;
|
|
1120
|
+
type credentials_CredentialRecord = CredentialRecord;
|
|
1106
1121
|
type credentials_CredentialServices = CredentialServices;
|
|
1107
1122
|
type credentials_ResolvedSecret = ResolvedSecret;
|
|
1108
1123
|
declare const credentials_credentialServices: typeof credentialServices;
|
|
@@ -1110,7 +1125,7 @@ declare const credentials_describeRef: typeof describeRef;
|
|
|
1110
1125
|
declare const credentials_resolveGithubToken: typeof resolveGithubToken;
|
|
1111
1126
|
declare const credentials_resolveRef: typeof resolveRef;
|
|
1112
1127
|
declare namespace credentials {
|
|
1113
|
-
export { type credentials_CredentialDescription as CredentialDescription, type credentials_CredentialServices as CredentialServices, type credentials_ResolvedSecret as ResolvedSecret, credentials_credentialServices as credentialServices, credentials_describeRef as describeRef, credentials_resolveGithubToken as resolveGithubToken, credentials_resolveRef as resolveRef };
|
|
1128
|
+
export { type credentials_CredentialDescription as CredentialDescription, type credentials_CredentialRecord as CredentialRecord, type credentials_CredentialServices as CredentialServices, GITHUB_RECORD_KEY$1 as GITHUB_RECORD_KEY, type credentials_ResolvedSecret as ResolvedSecret, credentials_credentialServices as credentialServices, credentials_describeRef as describeRef, credentials_resolveGithubToken as resolveGithubToken, credentials_resolveRef as resolveRef };
|
|
1114
1129
|
}
|
|
1115
1130
|
|
|
1116
1131
|
/**
|
|
@@ -1130,7 +1145,7 @@ interface GithubProbeResult {
|
|
|
1130
1145
|
}
|
|
1131
1146
|
declare function probeGithubAuth(options: {
|
|
1132
1147
|
credentials: CredentialServices | undefined;
|
|
1133
|
-
authMode: 'pat' | 'gh';
|
|
1148
|
+
authMode: 'pat' | 'gh' | 'browser';
|
|
1134
1149
|
tokenRef: string;
|
|
1135
1150
|
env?: NodeJS.ProcessEnv;
|
|
1136
1151
|
fetchImpl?: typeof fetch;
|
|
@@ -1142,6 +1157,153 @@ declare namespace github {
|
|
|
1142
1157
|
export { type github_GithubProbeResult as GithubProbeResult, github_probeGithubAuth as probeGithubAuth };
|
|
1143
1158
|
}
|
|
1144
1159
|
|
|
1160
|
+
/** 与核心 `@deepseek-ai/dsh-github-auth` 提案保持同 key;迁移时消费端无需改。 */
|
|
1161
|
+
declare const GITHUB_RECORD_KEY = "github/default";
|
|
1162
|
+
/** 本地回调路径(带集成前缀,避免多 provider 撞 webServer 的 `(kind,path)` 唯一约束)。 */
|
|
1163
|
+
declare const CALLBACK_PATH = "/api/morehao/github-trending/oauth/github/callback";
|
|
1164
|
+
/**
|
|
1165
|
+
* 内置的 GitHub OAuth App 公开 client_id(**非机密**:OAuth 的 client_id 设计上就是公开的,
|
|
1166
|
+
* 会出现在授权 URL 里,与 `gh`/VS Code 内置自己的 client_id 同理)。
|
|
1167
|
+
* 纯 PKCE public client,无需 client_secret。用户/部署可在 `apply(config.githubClientId)` 覆盖。
|
|
1168
|
+
*
|
|
1169
|
+
* OAuth App 侧登记的回调:`http://127.0.0.1/api/morehao/github-trending/oauth/github/callback`
|
|
1170
|
+
* (loopback 按 RFC 8252 忽略端口,运行时随机端口可正常回跳)。
|
|
1171
|
+
*/
|
|
1172
|
+
declare const DEFAULT_GITHUB_CLIENT_ID = "Ov23li7rhp0EKoqbkJKY";
|
|
1173
|
+
/** 解析生效的 client_id:显式 config 优先,否则用内置默认。 */
|
|
1174
|
+
declare function resolveClientId(deps: GithubAuthDeps): string | undefined;
|
|
1175
|
+
/** 本插件运行需要 push / 建 PR,`read:user` 不够;一次授权到 `repo`。 */
|
|
1176
|
+
declare const DEFAULT_SCOPE = "repo";
|
|
1177
|
+
interface AuthorizationFlow {
|
|
1178
|
+
key: string;
|
|
1179
|
+
label: string;
|
|
1180
|
+
methods: Array<{
|
|
1181
|
+
id: string;
|
|
1182
|
+
label: string;
|
|
1183
|
+
}>;
|
|
1184
|
+
run: (session: AuthorizationSessionLike) => Promise<void> | void;
|
|
1185
|
+
}
|
|
1186
|
+
interface AuthorizationSessionLike {
|
|
1187
|
+
method?: string;
|
|
1188
|
+
signal?: AbortSignal;
|
|
1189
|
+
notify?: (notice: {
|
|
1190
|
+
message: string;
|
|
1191
|
+
url?: string;
|
|
1192
|
+
code?: string;
|
|
1193
|
+
}) => void;
|
|
1194
|
+
prompt?: (prompt: unknown) => Promise<unknown>;
|
|
1195
|
+
commit?: (record: {
|
|
1196
|
+
kind: 'grant';
|
|
1197
|
+
payload: unknown;
|
|
1198
|
+
}) => Promise<unknown>;
|
|
1199
|
+
}
|
|
1200
|
+
interface AuthorizationLike {
|
|
1201
|
+
registerFlow?: (flow: AuthorizationFlow) => unknown;
|
|
1202
|
+
begin?: (request: unknown) => Promise<{
|
|
1203
|
+
status: 'authorized' | 'cancelled';
|
|
1204
|
+
}>;
|
|
1205
|
+
cancel?: (key: string) => void;
|
|
1206
|
+
}
|
|
1207
|
+
interface WebServerLike {
|
|
1208
|
+
register?: (route: {
|
|
1209
|
+
kind: 'exact';
|
|
1210
|
+
path: string;
|
|
1211
|
+
handler: (req: any, res: any) => void;
|
|
1212
|
+
}) => () => void;
|
|
1213
|
+
port?: number;
|
|
1214
|
+
host?: string;
|
|
1215
|
+
}
|
|
1216
|
+
type FetchLike = typeof fetch;
|
|
1217
|
+
interface GithubAuthDeps {
|
|
1218
|
+
get: (name: string) => unknown;
|
|
1219
|
+
fetchImpl?: FetchLike;
|
|
1220
|
+
logger?: (event: string, data?: Record<string, unknown>) => void;
|
|
1221
|
+
/** 覆盖端点(测试用);生产留空走 GitHub 官方。 */
|
|
1222
|
+
endpoints?: {
|
|
1223
|
+
authorize?: string;
|
|
1224
|
+
accessToken?: string;
|
|
1225
|
+
deviceCode?: string;
|
|
1226
|
+
user?: string;
|
|
1227
|
+
};
|
|
1228
|
+
/** GitHub OAuth App 的 public client_id(无需 secret)。 */
|
|
1229
|
+
clientId?: string;
|
|
1230
|
+
scope?: string;
|
|
1231
|
+
attemptTimeoutMs?: number;
|
|
1232
|
+
}
|
|
1233
|
+
type GithubAttemptPhase = 'idle' | 'waiting-browser' | 'waiting-device' | 'exchanging' | 'done' | 'error';
|
|
1234
|
+
interface GithubAttemptView {
|
|
1235
|
+
phase: GithubAttemptPhase;
|
|
1236
|
+
authorizeUrl?: string;
|
|
1237
|
+
verificationUri?: string;
|
|
1238
|
+
userCode?: string;
|
|
1239
|
+
expiresAt?: number;
|
|
1240
|
+
login?: string;
|
|
1241
|
+
error?: string;
|
|
1242
|
+
}
|
|
1243
|
+
declare function attemptView(): GithubAttemptView;
|
|
1244
|
+
/** PKCE:返回 verifier 与 S256 challenge。 */
|
|
1245
|
+
declare function createPkce(): {
|
|
1246
|
+
verifier: string;
|
|
1247
|
+
challenge: string;
|
|
1248
|
+
};
|
|
1249
|
+
/** 常量时间比较 state(长度不等直接 false,避免 timingSafeEqual 抛错)。 */
|
|
1250
|
+
declare function verifyState(received: string, expected: string): boolean;
|
|
1251
|
+
/** 拼授权页 URL。 */
|
|
1252
|
+
declare function buildAuthorizeUrl(params: {
|
|
1253
|
+
authorizeEndpoint: string;
|
|
1254
|
+
clientId: string;
|
|
1255
|
+
redirectUri: string;
|
|
1256
|
+
scope: string;
|
|
1257
|
+
state: string;
|
|
1258
|
+
challenge: string;
|
|
1259
|
+
}): string;
|
|
1260
|
+
interface GithubGrantPayload {
|
|
1261
|
+
version: 1;
|
|
1262
|
+
token: string;
|
|
1263
|
+
login?: string;
|
|
1264
|
+
tokenType?: string;
|
|
1265
|
+
scope?: string;
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* 向 `ctx.authorization` 注册 GitHub flow(若核心没有该服务则降级 no-op)。
|
|
1269
|
+
* 在 `apply()` 里调用一次。
|
|
1270
|
+
*/
|
|
1271
|
+
declare function registerGithubFlow(deps: GithubAuthDeps): void;
|
|
1272
|
+
/**
|
|
1273
|
+
* 启动一次浏览器登录 attempt(进程内 `authorization.begin`,不阻塞请求)。
|
|
1274
|
+
* @returns 当前 attempt 视图(含 authorizeUrl,供前端打开)。二次调用在已有进行中时抛错。
|
|
1275
|
+
*/
|
|
1276
|
+
declare function beginGithubSignIn(deps: GithubAuthDeps, method?: 'browser' | 'device'): Promise<GithubAttemptView>;
|
|
1277
|
+
/** 取消进行中的 attempt。 */
|
|
1278
|
+
declare function cancelGithubSignIn(deps: GithubAuthDeps): void;
|
|
1279
|
+
/** 仅测试用:清空内存 attempt。 */
|
|
1280
|
+
declare function resetAttemptForTest(): void;
|
|
1281
|
+
|
|
1282
|
+
type githubAuth_AuthorizationFlow = AuthorizationFlow;
|
|
1283
|
+
type githubAuth_AuthorizationLike = AuthorizationLike;
|
|
1284
|
+
type githubAuth_AuthorizationSessionLike = AuthorizationSessionLike;
|
|
1285
|
+
declare const githubAuth_CALLBACK_PATH: typeof CALLBACK_PATH;
|
|
1286
|
+
declare const githubAuth_DEFAULT_GITHUB_CLIENT_ID: typeof DEFAULT_GITHUB_CLIENT_ID;
|
|
1287
|
+
declare const githubAuth_DEFAULT_SCOPE: typeof DEFAULT_SCOPE;
|
|
1288
|
+
declare const githubAuth_GITHUB_RECORD_KEY: typeof GITHUB_RECORD_KEY;
|
|
1289
|
+
type githubAuth_GithubAttemptPhase = GithubAttemptPhase;
|
|
1290
|
+
type githubAuth_GithubAttemptView = GithubAttemptView;
|
|
1291
|
+
type githubAuth_GithubAuthDeps = GithubAuthDeps;
|
|
1292
|
+
type githubAuth_GithubGrantPayload = GithubGrantPayload;
|
|
1293
|
+
type githubAuth_WebServerLike = WebServerLike;
|
|
1294
|
+
declare const githubAuth_attemptView: typeof attemptView;
|
|
1295
|
+
declare const githubAuth_beginGithubSignIn: typeof beginGithubSignIn;
|
|
1296
|
+
declare const githubAuth_buildAuthorizeUrl: typeof buildAuthorizeUrl;
|
|
1297
|
+
declare const githubAuth_cancelGithubSignIn: typeof cancelGithubSignIn;
|
|
1298
|
+
declare const githubAuth_createPkce: typeof createPkce;
|
|
1299
|
+
declare const githubAuth_registerGithubFlow: typeof registerGithubFlow;
|
|
1300
|
+
declare const githubAuth_resetAttemptForTest: typeof resetAttemptForTest;
|
|
1301
|
+
declare const githubAuth_resolveClientId: typeof resolveClientId;
|
|
1302
|
+
declare const githubAuth_verifyState: typeof verifyState;
|
|
1303
|
+
declare namespace githubAuth {
|
|
1304
|
+
export { type githubAuth_AuthorizationFlow as AuthorizationFlow, type githubAuth_AuthorizationLike as AuthorizationLike, type githubAuth_AuthorizationSessionLike as AuthorizationSessionLike, githubAuth_CALLBACK_PATH as CALLBACK_PATH, githubAuth_DEFAULT_GITHUB_CLIENT_ID as DEFAULT_GITHUB_CLIENT_ID, githubAuth_DEFAULT_SCOPE as DEFAULT_SCOPE, githubAuth_GITHUB_RECORD_KEY as GITHUB_RECORD_KEY, type githubAuth_GithubAttemptPhase as GithubAttemptPhase, type githubAuth_GithubAttemptView as GithubAttemptView, type githubAuth_GithubAuthDeps as GithubAuthDeps, type githubAuth_GithubGrantPayload as GithubGrantPayload, type githubAuth_WebServerLike as WebServerLike, githubAuth_attemptView as attemptView, githubAuth_beginGithubSignIn as beginGithubSignIn, githubAuth_buildAuthorizeUrl as buildAuthorizeUrl, githubAuth_cancelGithubSignIn as cancelGithubSignIn, githubAuth_createPkce as createPkce, githubAuth_registerGithubFlow as registerGithubFlow, githubAuth_resetAttemptForTest as resetAttemptForTest, githubAuth_resolveClientId as resolveClientId, githubAuth_verifyState as verifyState };
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1145
1307
|
interface DefaultBranchResult {
|
|
1146
1308
|
branch?: string;
|
|
1147
1309
|
/** `origin-head` = 远端默认分支;`head` = 本地当前分支兜底;`none` = 读不到。 */
|
|
@@ -1823,6 +1985,8 @@ interface RouteDeps {
|
|
|
1823
1985
|
/** 包名与仓库地址都来自 package.json,由 src/index.ts 传入。 */
|
|
1824
1986
|
pluginName?: string;
|
|
1825
1987
|
repository?: string;
|
|
1988
|
+
/** 浏览器 GitHub 登录用的 public client_id(由 apply 的 config 传入;缺省时按钮会提示未配置)。 */
|
|
1989
|
+
githubClientId?: string;
|
|
1826
1990
|
logger?: (event: string, data?: Record<string, unknown>) => void;
|
|
1827
1991
|
}
|
|
1828
1992
|
declare function createRoutes(deps: RouteDeps): (request: http.IncomingMessage, response: http.ServerResponse) => Promise<void>;
|
|
@@ -2145,4 +2309,4 @@ declare namespace tools {
|
|
|
2145
2309
|
export { tools_createTrendingHistoryTool as createTrendingHistoryTool, tools_createTrendingRunTool as createTrendingRunTool };
|
|
2146
2310
|
}
|
|
2147
2311
|
|
|
2148
|
-
export { channel, config, core, credentials, errors, github, handshake$1 as handshake, importCli, ipc, locate, paths, pendingPr, pluginInfo, preflight, proxy, quickfill, release, repo, routes, run, runner, schedule, state, subscribe, tools, update as updateService, version };
|
|
2312
|
+
export { channel, config, core, credentials, errors, github, githubAuth, handshake$1 as handshake, importCli, ipc, locate, paths, pendingPr, pluginInfo, preflight, proxy, quickfill, release, repo, routes, run, runner, schedule, state, subscribe, tools, update as updateService, version };
|
package/lib/internal.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
core_exports,
|
|
5
5
|
credentials_exports,
|
|
6
6
|
errors_exports,
|
|
7
|
+
github_auth_exports,
|
|
7
8
|
github_exports,
|
|
8
9
|
handshake_exports,
|
|
9
10
|
import_cli_exports,
|
|
@@ -26,7 +27,7 @@ import {
|
|
|
26
27
|
tools_exports,
|
|
27
28
|
update_exports,
|
|
28
29
|
version_exports
|
|
29
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-LLTW4YHJ.js";
|
|
30
31
|
|
|
31
32
|
// src/internal.ts
|
|
32
33
|
import * as exec from "@morehao/dsh-plugin-kit/exec";
|
|
@@ -38,6 +39,7 @@ export {
|
|
|
38
39
|
errors_exports as errors,
|
|
39
40
|
exec,
|
|
40
41
|
github_exports as github,
|
|
42
|
+
github_auth_exports as githubAuth,
|
|
41
43
|
handshake_exports as handshake,
|
|
42
44
|
import_cli_exports as importCli,
|
|
43
45
|
ipc_exports as ipc,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morehao/dsh-github-trending",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "GitHub 趋势日报的 DeepSeek Harness 插件:按日 / 周 / 月周期定时 spawn 本地 trendradar 核心,抓取 Trending、生成中文简介与周期总结,并可选把订阅仓库的新 Release 合成中文摘要,一起提交到你的仓库、自动开 PR 合并;内置概览 / 榜单 / 运行记录 / 订阅 / 设置工作台。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|