@morehao/dsh-github-trending 0.7.11 → 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/client.js +123 -4
- 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 +1 -1
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,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 = () => {
|
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,140 @@ 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
|
+
/** 本插件运行需要 push / 建 PR,`read:user` 不够;一次授权到 `repo`。 */
|
|
1165
|
+
declare const DEFAULT_SCOPE = "repo";
|
|
1166
|
+
interface AuthorizationFlow {
|
|
1167
|
+
key: string;
|
|
1168
|
+
label: string;
|
|
1169
|
+
methods: Array<{
|
|
1170
|
+
id: string;
|
|
1171
|
+
label: string;
|
|
1172
|
+
}>;
|
|
1173
|
+
run: (session: AuthorizationSessionLike) => Promise<void> | void;
|
|
1174
|
+
}
|
|
1175
|
+
interface AuthorizationSessionLike {
|
|
1176
|
+
method?: string;
|
|
1177
|
+
signal?: AbortSignal;
|
|
1178
|
+
notify?: (notice: {
|
|
1179
|
+
message: string;
|
|
1180
|
+
url?: string;
|
|
1181
|
+
code?: string;
|
|
1182
|
+
}) => void;
|
|
1183
|
+
prompt?: (prompt: unknown) => Promise<unknown>;
|
|
1184
|
+
commit?: (record: {
|
|
1185
|
+
kind: 'grant';
|
|
1186
|
+
payload: unknown;
|
|
1187
|
+
}) => Promise<unknown>;
|
|
1188
|
+
}
|
|
1189
|
+
interface AuthorizationLike {
|
|
1190
|
+
registerFlow?: (flow: AuthorizationFlow) => unknown;
|
|
1191
|
+
begin?: (request: unknown) => Promise<{
|
|
1192
|
+
status: 'authorized' | 'cancelled';
|
|
1193
|
+
}>;
|
|
1194
|
+
cancel?: (key: string) => void;
|
|
1195
|
+
}
|
|
1196
|
+
interface WebServerLike {
|
|
1197
|
+
register?: (route: {
|
|
1198
|
+
kind: 'exact';
|
|
1199
|
+
path: string;
|
|
1200
|
+
handler: (req: any, res: any) => void;
|
|
1201
|
+
}) => () => void;
|
|
1202
|
+
port?: number;
|
|
1203
|
+
host?: string;
|
|
1204
|
+
}
|
|
1205
|
+
type FetchLike = typeof fetch;
|
|
1206
|
+
interface GithubAuthDeps {
|
|
1207
|
+
get: (name: string) => unknown;
|
|
1208
|
+
fetchImpl?: FetchLike;
|
|
1209
|
+
logger?: (event: string, data?: Record<string, unknown>) => void;
|
|
1210
|
+
/** 覆盖端点(测试用);生产留空走 GitHub 官方。 */
|
|
1211
|
+
endpoints?: {
|
|
1212
|
+
authorize?: string;
|
|
1213
|
+
accessToken?: string;
|
|
1214
|
+
deviceCode?: string;
|
|
1215
|
+
user?: string;
|
|
1216
|
+
};
|
|
1217
|
+
/** GitHub OAuth App 的 public client_id(无需 secret)。 */
|
|
1218
|
+
clientId?: string;
|
|
1219
|
+
scope?: string;
|
|
1220
|
+
attemptTimeoutMs?: number;
|
|
1221
|
+
}
|
|
1222
|
+
type GithubAttemptPhase = 'idle' | 'waiting-browser' | 'waiting-device' | 'exchanging' | 'done' | 'error';
|
|
1223
|
+
interface GithubAttemptView {
|
|
1224
|
+
phase: GithubAttemptPhase;
|
|
1225
|
+
authorizeUrl?: string;
|
|
1226
|
+
verificationUri?: string;
|
|
1227
|
+
userCode?: string;
|
|
1228
|
+
expiresAt?: number;
|
|
1229
|
+
login?: string;
|
|
1230
|
+
error?: string;
|
|
1231
|
+
}
|
|
1232
|
+
declare function attemptView(): GithubAttemptView;
|
|
1233
|
+
/** PKCE:返回 verifier 与 S256 challenge。 */
|
|
1234
|
+
declare function createPkce(): {
|
|
1235
|
+
verifier: string;
|
|
1236
|
+
challenge: string;
|
|
1237
|
+
};
|
|
1238
|
+
/** 常量时间比较 state(长度不等直接 false,避免 timingSafeEqual 抛错)。 */
|
|
1239
|
+
declare function verifyState(received: string, expected: string): boolean;
|
|
1240
|
+
/** 拼授权页 URL。 */
|
|
1241
|
+
declare function buildAuthorizeUrl(params: {
|
|
1242
|
+
authorizeEndpoint: string;
|
|
1243
|
+
clientId: string;
|
|
1244
|
+
redirectUri: string;
|
|
1245
|
+
scope: string;
|
|
1246
|
+
state: string;
|
|
1247
|
+
challenge: string;
|
|
1248
|
+
}): string;
|
|
1249
|
+
interface GithubGrantPayload {
|
|
1250
|
+
version: 1;
|
|
1251
|
+
token: string;
|
|
1252
|
+
login?: string;
|
|
1253
|
+
tokenType?: string;
|
|
1254
|
+
scope?: string;
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* 向 `ctx.authorization` 注册 GitHub flow(若核心没有该服务则降级 no-op)。
|
|
1258
|
+
* 在 `apply()` 里调用一次。
|
|
1259
|
+
*/
|
|
1260
|
+
declare function registerGithubFlow(deps: GithubAuthDeps): void;
|
|
1261
|
+
/**
|
|
1262
|
+
* 启动一次浏览器登录 attempt(进程内 `authorization.begin`,不阻塞请求)。
|
|
1263
|
+
* @returns 当前 attempt 视图(含 authorizeUrl,供前端打开)。二次调用在已有进行中时抛错。
|
|
1264
|
+
*/
|
|
1265
|
+
declare function beginGithubSignIn(deps: GithubAuthDeps, method?: 'browser' | 'device'): Promise<GithubAttemptView>;
|
|
1266
|
+
/** 取消进行中的 attempt。 */
|
|
1267
|
+
declare function cancelGithubSignIn(deps: GithubAuthDeps): void;
|
|
1268
|
+
/** 仅测试用:清空内存 attempt。 */
|
|
1269
|
+
declare function resetAttemptForTest(): void;
|
|
1270
|
+
|
|
1271
|
+
type githubAuth_AuthorizationFlow = AuthorizationFlow;
|
|
1272
|
+
type githubAuth_AuthorizationLike = AuthorizationLike;
|
|
1273
|
+
type githubAuth_AuthorizationSessionLike = AuthorizationSessionLike;
|
|
1274
|
+
declare const githubAuth_CALLBACK_PATH: typeof CALLBACK_PATH;
|
|
1275
|
+
declare const githubAuth_DEFAULT_SCOPE: typeof DEFAULT_SCOPE;
|
|
1276
|
+
declare const githubAuth_GITHUB_RECORD_KEY: typeof GITHUB_RECORD_KEY;
|
|
1277
|
+
type githubAuth_GithubAttemptPhase = GithubAttemptPhase;
|
|
1278
|
+
type githubAuth_GithubAttemptView = GithubAttemptView;
|
|
1279
|
+
type githubAuth_GithubAuthDeps = GithubAuthDeps;
|
|
1280
|
+
type githubAuth_GithubGrantPayload = GithubGrantPayload;
|
|
1281
|
+
type githubAuth_WebServerLike = WebServerLike;
|
|
1282
|
+
declare const githubAuth_attemptView: typeof attemptView;
|
|
1283
|
+
declare const githubAuth_beginGithubSignIn: typeof beginGithubSignIn;
|
|
1284
|
+
declare const githubAuth_buildAuthorizeUrl: typeof buildAuthorizeUrl;
|
|
1285
|
+
declare const githubAuth_cancelGithubSignIn: typeof cancelGithubSignIn;
|
|
1286
|
+
declare const githubAuth_createPkce: typeof createPkce;
|
|
1287
|
+
declare const githubAuth_registerGithubFlow: typeof registerGithubFlow;
|
|
1288
|
+
declare const githubAuth_resetAttemptForTest: typeof resetAttemptForTest;
|
|
1289
|
+
declare const githubAuth_verifyState: typeof verifyState;
|
|
1290
|
+
declare namespace githubAuth {
|
|
1291
|
+
export { type githubAuth_AuthorizationFlow as AuthorizationFlow, type githubAuth_AuthorizationLike as AuthorizationLike, type githubAuth_AuthorizationSessionLike as AuthorizationSessionLike, githubAuth_CALLBACK_PATH as CALLBACK_PATH, 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_verifyState as verifyState };
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1145
1294
|
interface DefaultBranchResult {
|
|
1146
1295
|
branch?: string;
|
|
1147
1296
|
/** `origin-head` = 远端默认分支;`head` = 本地当前分支兜底;`none` = 读不到。 */
|
|
@@ -1823,6 +1972,8 @@ interface RouteDeps {
|
|
|
1823
1972
|
/** 包名与仓库地址都来自 package.json,由 src/index.ts 传入。 */
|
|
1824
1973
|
pluginName?: string;
|
|
1825
1974
|
repository?: string;
|
|
1975
|
+
/** 浏览器 GitHub 登录用的 public client_id(由 apply 的 config 传入;缺省时按钮会提示未配置)。 */
|
|
1976
|
+
githubClientId?: string;
|
|
1826
1977
|
logger?: (event: string, data?: Record<string, unknown>) => void;
|
|
1827
1978
|
}
|
|
1828
1979
|
declare function createRoutes(deps: RouteDeps): (request: http.IncomingMessage, response: http.ServerResponse) => Promise<void>;
|
|
@@ -2145,4 +2296,4 @@ declare namespace tools {
|
|
|
2145
2296
|
export { tools_createTrendingHistoryTool as createTrendingHistoryTool, tools_createTrendingRunTool as createTrendingRunTool };
|
|
2146
2297
|
}
|
|
2147
2298
|
|
|
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 };
|
|
2299
|
+
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-6PVBPJXO.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.0",
|
|
4
4
|
"description": "GitHub 趋势日报的 DeepSeek Harness 插件:按日 / 周 / 月周期定时 spawn 本地 trendradar 核心,抓取 Trending、生成中文简介与周期总结,并可选把订阅仓库的新 Release 合成中文摘要,一起提交到你的仓库、自动开 PR 合并;内置概览 / 榜单 / 运行记录 / 订阅 / 设置工作台。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|