@morehao/dsh-github-trending 0.8.3 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.8.4] - 2026-11-02
4
+
5
+ ### Features
6
+
7
+ - **auth**: open device-code page with prefilled code
8
+ - **auth**: 美化 GitHub OAuth 回调落地页 (#78)
9
+
10
+ **Full Changelog**: https://github.com/morehao/dsh-github-trending/compare/v0.8.3...v0.8.4
11
+
3
12
  ## [v0.8.3] - 2026-11-02
4
13
 
5
14
  ### Fixes
package/client.js CHANGED
@@ -125,10 +125,10 @@ var DICT = {
125
125
  "auth.gh": "本机 gh",
126
126
  "auth.browser": "浏览器登录",
127
127
  "auth.gh.hint": "使用本机 gh 登录态:终端执行 `gh auth login` 完成登录,再点「测试认证」。",
128
- "auth.browser.hint": "在浏览器里完成 GitHub 授权,token 由 DSH 安全保存,无需手工粘贴。默认走设备码:点一下后按提示输入验证码。",
128
+ "auth.browser.hint": "在浏览器里完成 GitHub 授权,token 由 DSH 安全保存,无需手工粘贴。点一下会在浏览器打开授权页(验证码通常已自动填入),确认授权即可。",
129
129
  "auth.browser.signin": "用 GitHub 登录",
130
130
  "auth.browser.waiting": "等待浏览器授权…",
131
- "auth.browser.code": "在打开的页面输入验证码:",
131
+ "auth.browser.code": "如未自动填入,请手动输入验证码:",
132
132
  "auth.browser.cancel": "取消",
133
133
  "auth.browser.done": "已登录",
134
134
  "auth.browser.device": "用设备码登录",
@@ -670,10 +670,10 @@ var DICT = {
670
670
  "auth.gh": "Local gh",
671
671
  "auth.browser": "Browser login",
672
672
  "auth.gh.hint": "Uses your local gh login: run `gh auth login` in a terminal, then click Test authentication.",
673
- "auth.browser.hint": "Authorize GitHub in your browser; DSH stores the token securely, no manual paste. Uses a device code by default: enter the code on the GitHub page after clicking.",
673
+ "auth.browser.hint": "Authorize GitHub in your browser; DSH stores the token securely, no manual paste. Clicking opens the authorization page with the code usually pre-filled — just confirm.",
674
674
  "auth.browser.signin": "Sign in with GitHub",
675
675
  "auth.browser.waiting": "Waiting for browser authorization…",
676
- "auth.browser.code": "Enter this code on the opened page:",
676
+ "auth.browser.code": "If it is not filled in automatically, enter this code:",
677
677
  "auth.browser.cancel": "Cancel",
678
678
  "auth.browser.done": "Signed in",
679
679
  "auth.browser.device": "Use a device code",
@@ -2272,9 +2272,10 @@ function SettingsView(props) {
2272
2272
  } catch {
2273
2273
  }
2274
2274
  }
2275
- if (view.phase === "waiting-device" && view.verificationUri !== void 0) {
2275
+ const deviceUrl = view.verificationUriComplete ?? view.verificationUri;
2276
+ if (view.phase === "waiting-device" && deviceUrl !== void 0) {
2276
2277
  try {
2277
- window.open(view.verificationUri, "_blank", "noopener,noreferrer");
2278
+ window.open(deviceUrl, "_blank", "noopener,noreferrer");
2278
2279
  } catch {
2279
2280
  }
2280
2281
  }
@@ -2583,10 +2584,19 @@ function SettingsView(props) {
2583
2584
  " ",
2584
2585
  h(Btn, { small: true, variant: "ghost", onClick: () => copyAuthorizeLink() }, t("auth.browser.copy"))
2585
2586
  ) : null,
2586
- signIn.verificationUri !== void 0 ? h(
2587
+ signIn.verificationUriComplete !== void 0 || signIn.verificationUri !== void 0 ? h(
2587
2588
  "div",
2588
2589
  { className: "dshgt-desc" },
2589
- h("a", { className: "dshgt-link", href: signIn.verificationUri, target: "_blank", rel: "noreferrer" }, signIn.verificationUri)
2590
+ h(
2591
+ "a",
2592
+ {
2593
+ className: "dshgt-link",
2594
+ href: signIn.verificationUriComplete ?? signIn.verificationUri,
2595
+ target: "_blank",
2596
+ rel: "noreferrer"
2597
+ },
2598
+ t("auth.browser.open")
2599
+ )
2590
2600
  ) : null,
2591
2601
  signIn.userCode !== void 0 ? h("div", { className: "dshgt-mono" }, `${t("auth.browser.code")} ${signIn.userCode}`) : null
2592
2602
  ) : null,
@@ -3540,8 +3540,10 @@ __export(github_auth_exports, {
3540
3540
  createPkce: () => createPkce,
3541
3541
  ensureGithubFlow: () => ensureGithubFlow,
3542
3542
  isGithubFlowRegistered: () => isGithubFlowRegistered,
3543
+ isHttpUrl: () => isHttpUrl,
3543
3544
  isLoopbackSignInAvailable: () => isLoopbackSignInAvailable,
3544
3545
  registerGithubFlow: () => registerGithubFlow,
3546
+ renderCallbackPage: () => renderCallbackPage,
3545
3547
  resetAttemptForTest: () => resetAttemptForTest,
3546
3548
  resolveClientId: () => resolveClientId,
3547
3549
  resolveClientSecret: () => resolveClientSecret,
@@ -3581,6 +3583,16 @@ function attemptView() {
3581
3583
  function base64url(buf) {
3582
3584
  return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
3583
3585
  }
3586
+ function isHttpUrl(value) {
3587
+ if (typeof value !== "string" || value === "") return false;
3588
+ let url;
3589
+ try {
3590
+ url = new URL(value);
3591
+ } catch {
3592
+ return false;
3593
+ }
3594
+ return url.protocol === "https:" || url.protocol === "http:";
3595
+ }
3584
3596
  function createPkce() {
3585
3597
  const verifier = base64url(randomBytes2(32));
3586
3598
  const challenge = base64url(createHash3("sha256").update(verifier).digest());
@@ -3591,6 +3603,41 @@ function verifyState(received, expected) {
3591
3603
  const b = Buffer.from(expected);
3592
3604
  return a.length === b.length && timingSafeEqual2(a, b);
3593
3605
  }
3606
+ function renderCallbackPage(kind) {
3607
+ const ok = kind === "success";
3608
+ const title = ok ? "\u767B\u5F55\u5B8C\u6210" : "\u767B\u5F55\u672A\u5B8C\u6210";
3609
+ const heading = ok ? "GitHub \u6388\u6743\u6210\u529F" : "GitHub \u6388\u6743\u5931\u8D25";
3610
+ const body = ok ? "\u5DF2\u5B8C\u6210\u767B\u5F55\uFF0C\u53EF\u4EE5\u5173\u95ED\u6B64\u9875\u9762\u3002" : "\u6388\u6743\u6821\u9A8C\u672A\u901A\u8FC7\uFF0C\u8BF7\u56DE\u5230 DSH \u91CD\u65B0\u53D1\u8D77\u767B\u5F55\u3002";
3611
+ const accent = ok ? "#1f883d" : "#cf222e";
3612
+ const icon = ok ? '<path d="M20 6 9 17l-5-5"/>' : '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>';
3613
+ const autoClose = ok ? "<script>(function(){try{setTimeout(function(){window.close()},1200)}catch(e){}})()</script>" : "";
3614
+ return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8">
3615
+ <meta name="viewport" content="width=device-width,initial-scale=1">
3616
+ <meta name="color-scheme" content="light dark">
3617
+ <title>${title}</title>
3618
+ <style>
3619
+ :root{--bg:#f6f8fa;--card:#ffffff;--fg:#1f2328;--muted:#59636e;--border:#d1d9e0;--accent:${accent}}
3620
+ @media (prefers-color-scheme:dark){:root{--bg:#0d1117;--card:#151b23;--fg:#e6edf3;--muted:#9198a1;--border:#3d444d}}
3621
+ *{box-sizing:border-box}
3622
+ html,body{height:100%}
3623
+ body{margin:0;display:flex;align-items:center;justify-content:center;padding:24px;
3624
+ background:var(--bg);color:var(--fg);
3625
+ font:400 15px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif}
3626
+ main{width:100%;max-width:400px;background:var(--card);border:1px solid var(--border);
3627
+ border-radius:14px;padding:40px 32px;text-align:center;
3628
+ box-shadow:0 8px 24px rgba(0,0,0,.06)}
3629
+ .mark{width:64px;height:64px;margin:0 auto 20px;border-radius:50%;display:flex;
3630
+ align-items:center;justify-content:center;background:color-mix(in srgb,var(--accent) 14%,transparent);color:var(--accent)}
3631
+ .mark svg{width:30px;height:30px;fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}
3632
+ h1{margin:0 0 8px;font-size:19px;font-weight:600;letter-spacing:.2px}
3633
+ p{margin:0;color:var(--muted);font-size:14px}
3634
+ </style></head>
3635
+ <body><main role="status">
3636
+ <div class="mark"><svg viewBox="0 0 24 24" aria-hidden="true">${icon}</svg></div>
3637
+ <h1>${heading}</h1>
3638
+ <p>${body}</p>
3639
+ </main>${autoClose}</body></html>`;
3640
+ }
3594
3641
  function buildAuthorizeUrl(params) {
3595
3642
  const url = new URL(params.authorizeEndpoint);
3596
3643
  url.searchParams.set("client_id", params.clientId);
@@ -3684,10 +3731,10 @@ async function runBrowser(deps, session) {
3684
3731
  const single = url.searchParams.getAll("state").length === 1 && url.searchParams.getAll("code").length === 1;
3685
3732
  const bad = req.method !== "GET" || url.pathname !== CALLBACK_PATH || !single || code === "" || !verifyState(gotState, state);
3686
3733
  if (bad) {
3687
- res.writeHead(400, { "cache-control": "no-store" }).end();
3734
+ res.writeHead(400, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" }).end(renderCallbackPage("error"));
3688
3735
  return;
3689
3736
  }
3690
- 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>');
3737
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" }).end(renderCallbackPage("success"));
3691
3738
  resolveCode(code);
3692
3739
  }
3693
3740
  });
@@ -3750,15 +3797,17 @@ async function runDevice(deps, session) {
3750
3797
  const intervalSec = typeof start.interval === "number" && start.interval > 0 ? start.interval : 5;
3751
3798
  const expiresInSec = typeof start.expires_in === "number" && start.expires_in > 0 ? start.expires_in : 900;
3752
3799
  const deadline = Date.now() + expiresInSec * 1e3;
3800
+ const verificationUriComplete = isHttpUrl(start.verification_uri_complete) ? start.verification_uri_complete : void 0;
3753
3801
  updateCurrent({
3754
3802
  phase: "waiting-device",
3755
3803
  verificationUri: start.verification_uri,
3804
+ ...verificationUriComplete === void 0 ? {} : { verificationUriComplete },
3756
3805
  userCode: start.user_code,
3757
3806
  expiresAt: deadline
3758
3807
  });
3759
3808
  session.notify?.({
3760
3809
  message: "Enter this code on the verification page to finish signing in.",
3761
- url: start.verification_uri,
3810
+ url: verificationUriComplete ?? start.verification_uri,
3762
3811
  code: start.user_code
3763
3812
  });
3764
3813
  let waitMs = intervalSec * 1e3;
@@ -3880,7 +3929,7 @@ async function beginGithubSignIn(deps, method = "browser") {
3880
3929
  interaction: {
3881
3930
  notify: (notice) => {
3882
3931
  if (notice.code !== void 0 && notice.url !== void 0) {
3883
- updateCurrent({ phase: "waiting-device", verificationUri: notice.url, userCode: notice.code });
3932
+ updateCurrent({ phase: "waiting-device", userCode: notice.code });
3884
3933
  } else if (notice.url !== void 0) {
3885
3934
  updateCurrent({ phase: "waiting-browser", authorizeUrl: notice.url });
3886
3935
  }
package/lib/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  createUpdateService,
10
10
  ensureGithubFlow,
11
11
  readPluginInfo
12
- } from "./chunk-VO72OOVK.js";
12
+ } from "./chunk-QOYTABJH.js";
13
13
 
14
14
  // src/index.ts
15
15
  var name = "dsh-github-trending";
package/lib/internal.d.ts CHANGED
@@ -1260,12 +1260,16 @@ interface GithubAttemptView {
1260
1260
  phase: GithubAttemptPhase;
1261
1261
  authorizeUrl?: string;
1262
1262
  verificationUri?: string;
1263
+ /** device flow 预填验证码的地址(GitHub 返回的 `verification_uri_complete`):打开只需点授权。 */
1264
+ verificationUriComplete?: string;
1263
1265
  userCode?: string;
1264
1266
  expiresAt?: number;
1265
1267
  login?: string;
1266
1268
  error?: string;
1267
1269
  }
1268
1270
  declare function attemptView(): GithubAttemptView;
1271
+ /** 判断一个未知值是否是可直接打开的 http(s) URL(用于校验 GitHub 返回的验证页地址)。 */
1272
+ declare function isHttpUrl(value: unknown): boolean;
1269
1273
  /** PKCE:返回 verifier 与 S256 challenge。 */
1270
1274
  declare function createPkce(): {
1271
1275
  verifier: string;
@@ -1273,6 +1277,13 @@ declare function createPkce(): {
1273
1277
  };
1274
1278
  /** 常量时间比较 state(长度不等直接 false,避免 timingSafeEqual 抛错)。 */
1275
1279
  declare function verifyState(received: string, expected: string): boolean;
1280
+ /** 回调落地页的两种结果(纯展示,无外部资源,离线可测)。 */
1281
+ type CallbackPageKind = 'success' | 'error';
1282
+ /**
1283
+ * 回环回调的落地页 HTML。自包含、无外链资源(CSP 友好、离线可用),
1284
+ * 跟随系统深浅色;成功页会尝试自动关闭本窗口(脚本失败时文字兜底)。
1285
+ */
1286
+ declare function renderCallbackPage(kind: CallbackPageKind): string;
1276
1287
  /** 拼授权页 URL。 */
1277
1288
  declare function buildAuthorizeUrl(params: {
1278
1289
  authorizeEndpoint: string;
@@ -1344,15 +1355,17 @@ declare const githubAuth_cancelGithubSignIn: typeof cancelGithubSignIn;
1344
1355
  declare const githubAuth_createPkce: typeof createPkce;
1345
1356
  declare const githubAuth_ensureGithubFlow: typeof ensureGithubFlow;
1346
1357
  declare const githubAuth_isGithubFlowRegistered: typeof isGithubFlowRegistered;
1358
+ declare const githubAuth_isHttpUrl: typeof isHttpUrl;
1347
1359
  declare const githubAuth_isLoopbackSignInAvailable: typeof isLoopbackSignInAvailable;
1348
1360
  declare const githubAuth_registerGithubFlow: typeof registerGithubFlow;
1361
+ declare const githubAuth_renderCallbackPage: typeof renderCallbackPage;
1349
1362
  declare const githubAuth_resetAttemptForTest: typeof resetAttemptForTest;
1350
1363
  declare const githubAuth_resolveClientId: typeof resolveClientId;
1351
1364
  declare const githubAuth_resolveClientSecret: typeof resolveClientSecret;
1352
1365
  declare const githubAuth_resolveMethod: typeof resolveMethod;
1353
1366
  declare const githubAuth_verifyState: typeof verifyState;
1354
1367
  declare namespace githubAuth {
1355
- 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_ensureGithubFlow as ensureGithubFlow, githubAuth_isGithubFlowRegistered as isGithubFlowRegistered, githubAuth_isLoopbackSignInAvailable as isLoopbackSignInAvailable, githubAuth_registerGithubFlow as registerGithubFlow, githubAuth_resetAttemptForTest as resetAttemptForTest, githubAuth_resolveClientId as resolveClientId, githubAuth_resolveClientSecret as resolveClientSecret, githubAuth_resolveMethod as resolveMethod, githubAuth_verifyState as verifyState };
1368
+ 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_ensureGithubFlow as ensureGithubFlow, githubAuth_isGithubFlowRegistered as isGithubFlowRegistered, githubAuth_isHttpUrl as isHttpUrl, githubAuth_isLoopbackSignInAvailable as isLoopbackSignInAvailable, githubAuth_registerGithubFlow as registerGithubFlow, githubAuth_renderCallbackPage as renderCallbackPage, githubAuth_resetAttemptForTest as resetAttemptForTest, githubAuth_resolveClientId as resolveClientId, githubAuth_resolveClientSecret as resolveClientSecret, githubAuth_resolveMethod as resolveMethod, githubAuth_verifyState as verifyState };
1356
1369
  }
1357
1370
 
1358
1371
  interface DefaultBranchResult {
package/lib/internal.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  tools_exports,
28
28
  update_exports,
29
29
  version_exports
30
- } from "./chunk-VO72OOVK.js";
30
+ } from "./chunk-QOYTABJH.js";
31
31
 
32
32
  // src/internal.ts
33
33
  import * as exec from "@morehao/dsh-plugin-kit/exec";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morehao/dsh-github-trending",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "GitHub 趋势日报的 DeepSeek Harness 插件:按日 / 周 / 月周期定时 spawn 本地 trendradar 核心,抓取 Trending、生成中文简介与周期总结,并可选把订阅仓库的新 Release 合成中文摘要,一起提交到你的仓库、自动开 PR 合并;内置概览 / 榜单 / 运行记录 / 订阅 / 设置工作台。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",