@morehao/dsh-github-trending 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +5 -4
- package/client.js +230 -184
- package/lib/{chunk-NMINPPWO.js → chunk-QXAKMNHQ.js} +143 -35
- package/lib/index.js +4 -4
- package/lib/internal.d.ts +66 -16
- package/lib/internal.js +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 - 2026-10-31
|
|
4
|
+
|
|
5
|
+
### 变更
|
|
6
|
+
|
|
7
|
+
- **适配核心 0.4.0 的三周期产物布局**:`dsh.core.version` 升到 `0.4.0`。核心产物改为 `trending/<period>/<bucket>/<name>.{md,json}`(daily/weekly/monthly),分支为 `trending/<period>/<name>`;插件每次运行按标的周期传 `run --period daily|weekly|monthly`,保证「抓取窗口 = 标注周期」。
|
|
8
|
+
- **预览按周期选取**:从本次 artifact 里挑本期 digest 的 `.md`(daily=`YYYY-MM-DD.md`、weekly=`YYYY-Wnn.md`、monthly=`YYYY-MM.md`),并排除 `index.md`;日榜预览行为不变。
|
|
9
|
+
- **调度按周期**:新增 `schedule.periods`(日/周/月多选,默认 `daily`)。日榜每天、周榜周日(ISO 周末)、月榜月末触发;去重键含周期与本地日期,同一周期当天只成功一次;「下次运行」取所有已启用周期里最近的一次。
|
|
10
|
+
- `trending_run` 工具与 `POST /run` 增加可选 `period` 参数(缺省用 `trending.since`)。
|
|
11
|
+
|
|
12
|
+
## 0.3.2 - 2026-10-31
|
|
13
|
+
|
|
14
|
+
### 改进
|
|
15
|
+
|
|
16
|
+
- **更新入口更显眼**:概览页新增「插件更新」卡片——当前 / 最新版本、检查更新、更新到 vX、本地安装时的手动命令都在这里;侧栏包名在有新版时显示红点。关于弹层与概览共用同一个 controller(模块级单例),打开多个位置不会重复请求。
|
|
17
|
+
- 关于弹层的更新区块改为复用同一组件(`client/update-panel.ts`),行为不变。
|
|
18
|
+
|
|
3
19
|
## 0.3.1 - 2026-10-31
|
|
4
20
|
|
|
5
21
|
### 新增
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://github.com/morehao/dsh-github-trending/actions/workflows/check.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/@morehao/dsh-github-trending)
|
|
5
5
|
|
|
6
|
-
> GitHub
|
|
6
|
+
> GitHub 趋势榜的 **DSH 入口**:按日/周/月周期定时 spawn 本地 [`trendradar`](https://github.com/morehao/trendradar) 核心,抓取 Trending、生成中文简介与周期总结,提交到你自己的仓库并自动合并 PR。
|
|
7
7
|
|
|
8
8
|
**状态:0.3.0。** 插件侧(配置、调度、核心调用、状态与工作台、两个工具)已完成。核心二进制走 **GitHub Release**:首次运行按 `os/arch` 下载、用 `checksums.txt` 校验 sha256、缓存到插件数据目录,之后离线复用。核心仓库打 tag 出资产之前,运行会明确报 `CORE_NOT_DOWNLOADED` / `CORE_UNAVAILABLE` 并给出下载地址——不会写空文件,也不会退回旧内容。
|
|
9
9
|
|
|
@@ -94,9 +94,10 @@ dsh plugin --profile <profile> add @morehao/dsh-github-trending
|
|
|
94
94
|
| `pr.titleTemplate` | `chore(trending): add {date} digest` | 默认规则,界面不展示 |
|
|
95
95
|
| `github.authMode` | `gh` | `gh`(本机 gh 登录态)/ `pat` |
|
|
96
96
|
| `github.tokenRef` | `GITHUB_TOKEN` | `ctx.credentials` 引用名(`pat` 用) |
|
|
97
|
-
| `trending.since` / `languages` / `topN` | `daily` / `all,typescript,python,go,rust` / `25` |
|
|
97
|
+
| `trending.since` / `languages` / `topN` | `daily` / `all,typescript,python,go,rust` / `25` | `since` 是单次运行周期(`daily`/`weekly`/`monthly`,抓取窗口=标的周期由核心保证);实际支持范围由核心决定 |
|
|
98
98
|
| `output.dir` | `trending` | 仓库内相对目录 |
|
|
99
99
|
| `schedule.enabled` / `time` / `timezone` | `true` / `08:30:00` / `Asia/Shanghai` | 插件自己的 60 s tick 驱动 |
|
|
100
|
+
| `schedule.periods` | `daily` | 定时自动产出的周期(`daily`/`weekly`/`monthly` 多选):日=每天、周=周日、月=月末触发 |
|
|
100
101
|
| `corePath` | 空 | 等价于 `TRENDING_DIGEST_CORE`(仅开发/排障,界面不提供入口,用环境变量或直接改 `settings.json`;设了就完全不下载) |
|
|
101
102
|
| `coreAutoDownload` | `true` | 缓存缺失时是否自动下载核心;关掉后只报 `CORE_NOT_DOWNLOADED`,由你在设置页点「下载核心」 |
|
|
102
103
|
|
|
@@ -104,7 +105,7 @@ dsh plugin --profile <profile> add @morehao/dsh-github-trending
|
|
|
104
105
|
|
|
105
106
|
| 工具 | 参数 | 返回 |
|
|
106
107
|
|---|---|---|
|
|
107
|
-
| `trending_run` | `{ dryRun?: boolean, date?: string }` | 结构化摘要:文件数、commit/分支、PR、内容哈希、错误码与下一步 |
|
|
108
|
+
| `trending_run` | `{ dryRun?: boolean, date?: string, period?: 'daily'\|'weekly'\|'monthly' }` | 结构化摘要:文件数、commit/分支、PR、内容哈希、错误码与下一步 |
|
|
108
109
|
| `trending_history` | `{ limit?: number }` | 上次/下次运行、最近 N 条记录、核心版本、模型、未合并 PR |
|
|
109
110
|
|
|
110
111
|
工具名是稳定对外接口:**只新增不改名**。
|
|
@@ -141,7 +142,7 @@ dsh plugin --profile <profile> add @morehao/dsh-github-trending
|
|
|
141
142
|
1. **结果信封**:核心 `run --config <path> --json` 输出契约 v2 `RunResult`(`ipcSchema=2`,可选 `branch/commit/prUrl/contentHash`);插件只解析这一种形状,旧核心的 `runOutput` 不再兼容。
|
|
142
143
|
2. **密钥传输**:POSIX 上配置经 **FIFO** 交给核心(文件系统里不出现明文);Windows 无 FIFO,退化为 0600 临时文件并在读完后立即删除。
|
|
143
144
|
3. **GitHub token** 经子进程环境变量传给核心(核心只认 `GITHUB_TOKEN`/`GH_TOKEN`);不进 argv、不进配置、不进日志。
|
|
144
|
-
4. **调度**用插件自己的 60 s tick(宿主 `schedule`
|
|
145
|
+
4. **调度**用插件自己的 60 s tick(宿主 `schedule` 是会话本地提醒,覆盖不到无人值守场景)。按 `schedule.periods` 分别触发:日榜每天、周榜周日(ISO 周末)、月榜月末;去重键含周期与本地日期,同一周期当天只成功一次。
|
|
145
146
|
5. **二进制改为下载**(2026-10-30 决策 B):不再随包发 npm 平台分包。首次运行需要能访问 GitHub Release;断网环境请用 `TRENDING_DIGEST_CORE` 指向本地核心。核心版本由 `package.json` 的 `dsh.core.version` 固定(用 `node scripts/set-core-version.mjs <x.y.z>` 写入)。
|
|
146
147
|
6. **核心版本固定**:`package.json` 的 `dsh.core.version` 是首次运行下载哪个核心 Release 的唯一来源(用 `node scripts/set-core-version.mjs <x.y.z>` 写入);下载失败会给出下载地址与错误码,不会退回旧内容。
|
|
147
148
|
7. **预览副本是派生数据**:不是第二份真相;仓库里的 canonical 文件才是。旧记录没有 `preview` 元数据,日报里只能看到对应的空状态,不承诺回填。
|
package/client.js
CHANGED
|
@@ -137,10 +137,12 @@ var DICT = {
|
|
|
137
137
|
"scope.period": "周期",
|
|
138
138
|
"scope.daily": "每日",
|
|
139
139
|
"scope.weekly": "每周",
|
|
140
|
+
"scope.monthly": "每月",
|
|
140
141
|
"scope.languages": "语言",
|
|
141
142
|
"scope.topN": "每语言条数",
|
|
142
143
|
"card.schedule.title": "调度",
|
|
143
144
|
"schedule.enabled": "定时运行",
|
|
145
|
+
"schedule.periods": "自动产出周期",
|
|
144
146
|
"schedule.time": "时间",
|
|
145
147
|
"schedule.timezone": "时区",
|
|
146
148
|
"schedule.next": "下次运行",
|
|
@@ -357,7 +359,8 @@ var DICT = {
|
|
|
357
359
|
"about.update.manual": "当前是本地 {kind} 安装,不能一键更新(会把开发链接换成 npm 版)。手动更新:",
|
|
358
360
|
"about.update.copy": "复制命令",
|
|
359
361
|
"about.update.copied": "已复制",
|
|
360
|
-
"about.update.hint": "只检查 npm 上的 latest 稳定版;更新后需要重启 DSH。"
|
|
362
|
+
"about.update.hint": "只检查 npm 上的 latest 稳定版;更新后需要重启 DSH。",
|
|
363
|
+
"update.badge": "有新版本可用"
|
|
361
364
|
},
|
|
362
365
|
en: {
|
|
363
366
|
"section.title": "GitHub Trending",
|
|
@@ -451,10 +454,12 @@ var DICT = {
|
|
|
451
454
|
"scope.period": "Period",
|
|
452
455
|
"scope.daily": "Daily",
|
|
453
456
|
"scope.weekly": "Weekly",
|
|
457
|
+
"scope.monthly": "Monthly",
|
|
454
458
|
"scope.languages": "Languages",
|
|
455
459
|
"scope.topN": "Per language",
|
|
456
460
|
"card.schedule.title": "Schedule",
|
|
457
461
|
"schedule.enabled": "Scheduled runs",
|
|
462
|
+
"schedule.periods": "Auto periods",
|
|
458
463
|
"schedule.time": "Time",
|
|
459
464
|
"schedule.timezone": "Time zone",
|
|
460
465
|
"schedule.next": "Next run",
|
|
@@ -671,7 +676,8 @@ var DICT = {
|
|
|
671
676
|
"about.update.manual": "Installed from a local {kind} spec; one-click update is disabled (it would replace the dev link). Update manually:",
|
|
672
677
|
"about.update.copy": "Copy command",
|
|
673
678
|
"about.update.copied": "Copied",
|
|
674
|
-
"about.update.hint": "Checks the npm latest stable tag only; applying requires a DSH restart."
|
|
679
|
+
"about.update.hint": "Checks the npm latest stable tag only; applying requires a DSH restart.",
|
|
680
|
+
"update.badge": "Update available"
|
|
675
681
|
}
|
|
676
682
|
};
|
|
677
683
|
function activeLang(locale) {
|
|
@@ -952,6 +958,9 @@ var CSS = `
|
|
|
952
958
|
.dshgt-update-manual{margin-top:9px;padding:9px 10px;border:1px solid color-mix(in srgb,var(--gt-warn) 35%,var(--gt-border));border-radius:8px;background:color-mix(in srgb,var(--gt-warn) 6%,var(--gt-card));}
|
|
953
959
|
.dshgt-update-manual .dshgt-update-warn{margin:0 0 6px;}
|
|
954
960
|
.dshgt-update-cmd{display:block;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;line-height:1.5;color:var(--gt-fg);word-break:break-all;user-select:all;}
|
|
961
|
+
.dshgt-update-dot{display:inline-block;width:6px;height:6px;margin-left:6px;border-radius:50%;background:var(--gt-err);vertical-align:middle;flex:none;}
|
|
962
|
+
.dshgt-update-bare{margin-top:0;padding-top:0;border-top:0;}
|
|
963
|
+
.dshgt-update-card .dshgt-card-h{margin-bottom:2px;}
|
|
955
964
|
|
|
956
965
|
@media (max-width:1100px){
|
|
957
966
|
.dshgt-app{grid-template-columns:190px minmax(0,1fr);}
|
|
@@ -1658,10 +1667,11 @@ function SettingsView(props) {
|
|
|
1658
1667
|
value: draft.trending.since,
|
|
1659
1668
|
options: [
|
|
1660
1669
|
{ value: "daily", label: t("scope.daily") },
|
|
1661
|
-
{ value: "weekly", label: t("scope.weekly") }
|
|
1670
|
+
{ value: "weekly", label: t("scope.weekly") },
|
|
1671
|
+
{ value: "monthly", label: t("scope.monthly") }
|
|
1662
1672
|
],
|
|
1663
1673
|
onChange: (value) => update((next) => {
|
|
1664
|
-
next.trending.since = value === "weekly" ? "weekly" : "daily";
|
|
1674
|
+
next.trending.since = value === "weekly" ? "weekly" : value === "monthly" ? "monthly" : "daily";
|
|
1665
1675
|
})
|
|
1666
1676
|
}),
|
|
1667
1677
|
h("span", { className: "dshgt-hint" }, t("scope.topN")),
|
|
@@ -1699,6 +1709,23 @@ function SettingsView(props) {
|
|
|
1699
1709
|
next.schedule.timezone = value;
|
|
1700
1710
|
}) })
|
|
1701
1711
|
),
|
|
1712
|
+
h(
|
|
1713
|
+
Row,
|
|
1714
|
+
{ label: t("schedule.periods") },
|
|
1715
|
+
...["daily", "weekly", "monthly"].map(
|
|
1716
|
+
(period) => h(Toggle, {
|
|
1717
|
+
key: period,
|
|
1718
|
+
checked: draft.schedule.periods.includes(period),
|
|
1719
|
+
onChange: (on) => update((next) => {
|
|
1720
|
+
const set = new Set(next.schedule.periods);
|
|
1721
|
+
if (on) set.add(period);
|
|
1722
|
+
else set.delete(period);
|
|
1723
|
+
next.schedule.periods = ["daily", "weekly", "monthly"].filter((p) => set.has(p));
|
|
1724
|
+
}),
|
|
1725
|
+
label: t(`scope.${period}`)
|
|
1726
|
+
})
|
|
1727
|
+
)
|
|
1728
|
+
),
|
|
1702
1729
|
h("p", { className: "dshgt-hint" }, t("schedule.source"))
|
|
1703
1730
|
);
|
|
1704
1731
|
const currentCard = section === "source" ? sourceCard : section === "model" ? modelCard : section === "repo" ? repoCard : section === "auth" ? authCard : section === "scope" ? scopeCard : scheduleCard;
|
|
@@ -1995,6 +2022,196 @@ function LiveRun(props) {
|
|
|
1995
2022
|
);
|
|
1996
2023
|
}
|
|
1997
2024
|
|
|
2025
|
+
// node_modules/@morehao/dsh-plugin-kit/dist/update-client.js
|
|
2026
|
+
var IDLE = { status: "idle", view: null, error: null };
|
|
2027
|
+
function errorOf(error) {
|
|
2028
|
+
if (error !== null && typeof error === "object") {
|
|
2029
|
+
const value = error;
|
|
2030
|
+
const message = typeof value.message === "string" && value.message !== "" ? value.message : "操作失败";
|
|
2031
|
+
return typeof value.code === "string" ? { code: value.code, message } : { message };
|
|
2032
|
+
}
|
|
2033
|
+
return { message: String(error ?? "操作失败") };
|
|
2034
|
+
}
|
|
2035
|
+
function createUpdateController(options) {
|
|
2036
|
+
const now = options.now ?? Date.now;
|
|
2037
|
+
let state = { ...IDLE };
|
|
2038
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2039
|
+
let inFlight = null;
|
|
2040
|
+
function emit(next) {
|
|
2041
|
+
state = next;
|
|
2042
|
+
options.onChange?.(state);
|
|
2043
|
+
for (const listener of listeners) listener(state);
|
|
2044
|
+
}
|
|
2045
|
+
async function check(checkOptions = {}) {
|
|
2046
|
+
if (inFlight !== null) return;
|
|
2047
|
+
inFlight = "check";
|
|
2048
|
+
emit({ ...state, status: "checking", error: null });
|
|
2049
|
+
try {
|
|
2050
|
+
const view = await options.request.check(checkOptions);
|
|
2051
|
+
const checkedAt = new Date(now()).toISOString();
|
|
2052
|
+
if (view.checkFailed) {
|
|
2053
|
+
emit({
|
|
2054
|
+
status: "failed",
|
|
2055
|
+
view,
|
|
2056
|
+
error: { code: "CHECK_FAILED", message: "检查更新失败:拿不到 registry 上的最新版本" },
|
|
2057
|
+
checkedAt
|
|
2058
|
+
});
|
|
2059
|
+
} else {
|
|
2060
|
+
emit({ status: view.updateAvailable ? "available" : "current", view, error: null, checkedAt });
|
|
2061
|
+
}
|
|
2062
|
+
} catch (error) {
|
|
2063
|
+
emit({ ...state, status: "failed", error: errorOf(error) });
|
|
2064
|
+
} finally {
|
|
2065
|
+
inFlight = null;
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
async function apply2() {
|
|
2069
|
+
if (inFlight !== null) return;
|
|
2070
|
+
const view = state.view;
|
|
2071
|
+
if (view === null || !view.updateAvailable) {
|
|
2072
|
+
emit({ ...state, status: "failed", error: { code: "NO_UPDATE", message: "没有可更新的版本,请先检查更新" } });
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
if (!view.canApply) {
|
|
2076
|
+
emit({
|
|
2077
|
+
...state,
|
|
2078
|
+
status: "failed",
|
|
2079
|
+
error: { code: "NO_APPLY", message: `当前环境不支持一键更新,请执行:${view.manualCommand}` }
|
|
2080
|
+
});
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
2083
|
+
inFlight = "apply";
|
|
2084
|
+
emit({ ...state, status: "applying", error: null });
|
|
2085
|
+
try {
|
|
2086
|
+
const result = await options.request.apply();
|
|
2087
|
+
const restartRequired = result.restartRequired ?? result.application !== "applied";
|
|
2088
|
+
emit({ ...state, status: restartRequired ? "restart-required" : "applied", error: null });
|
|
2089
|
+
} catch (error) {
|
|
2090
|
+
emit({ ...state, status: "failed", error: errorOf(error) });
|
|
2091
|
+
} finally {
|
|
2092
|
+
inFlight = null;
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
return {
|
|
2096
|
+
getState: () => state,
|
|
2097
|
+
subscribe: (listener) => {
|
|
2098
|
+
listeners.add(listener);
|
|
2099
|
+
return () => {
|
|
2100
|
+
listeners.delete(listener);
|
|
2101
|
+
};
|
|
2102
|
+
},
|
|
2103
|
+
check,
|
|
2104
|
+
apply: apply2,
|
|
2105
|
+
reset: () => emit({ ...IDLE })
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
// client/update-panel.ts
|
|
2110
|
+
var singleton = null;
|
|
2111
|
+
function getUpdateController() {
|
|
2112
|
+
if (singleton === null) {
|
|
2113
|
+
singleton = createUpdateController({
|
|
2114
|
+
request: {
|
|
2115
|
+
check: (options) => getPluginUpdate(options.fresh === true ? { fresh: true } : {}),
|
|
2116
|
+
apply: () => applyPluginUpdate()
|
|
2117
|
+
}
|
|
2118
|
+
});
|
|
2119
|
+
}
|
|
2120
|
+
return singleton;
|
|
2121
|
+
}
|
|
2122
|
+
function usePluginUpdate() {
|
|
2123
|
+
const controller = getUpdateController();
|
|
2124
|
+
const [state, setState] = import_react.default.useState(() => controller.getState());
|
|
2125
|
+
import_react.default.useEffect(() => {
|
|
2126
|
+
const unsubscribe = controller.subscribe(setState);
|
|
2127
|
+
if (controller.getState().status === "idle") void controller.check();
|
|
2128
|
+
return unsubscribe;
|
|
2129
|
+
}, [controller]);
|
|
2130
|
+
return [state, controller];
|
|
2131
|
+
}
|
|
2132
|
+
function UpdateDot(props) {
|
|
2133
|
+
const [state] = usePluginUpdate();
|
|
2134
|
+
if (state.view?.updateAvailable !== true) return null;
|
|
2135
|
+
return h("span", { className: "dshgt-update-dot", title: props.t("update.badge") });
|
|
2136
|
+
}
|
|
2137
|
+
function PluginUpdateSection(props) {
|
|
2138
|
+
const { t } = props;
|
|
2139
|
+
const [state, controller] = usePluginUpdate();
|
|
2140
|
+
const [confirming, setConfirming] = import_react.default.useState(false);
|
|
2141
|
+
const [copied, setCopied] = import_react.default.useState(false);
|
|
2142
|
+
import_react.default.useEffect(() => {
|
|
2143
|
+
if (state.status === "applying" || state.status === "restart-required" || state.status === "applied") setConfirming(false);
|
|
2144
|
+
}, [state.status]);
|
|
2145
|
+
const copyCommand = () => {
|
|
2146
|
+
const command = state.view?.manualCommand;
|
|
2147
|
+
if (command === void 0) return;
|
|
2148
|
+
void copyText(command).then((ok) => {
|
|
2149
|
+
if (!ok) return;
|
|
2150
|
+
setCopied(true);
|
|
2151
|
+
setTimeout(() => setCopied(false), 1600);
|
|
2152
|
+
});
|
|
2153
|
+
};
|
|
2154
|
+
const view = state.view;
|
|
2155
|
+
const status = state.status;
|
|
2156
|
+
const target = view !== null && view.updateAvailable ? view.latestVersion ?? "" : "";
|
|
2157
|
+
const busy = status === "checking" || status === "applying";
|
|
2158
|
+
const settled = status === "restart-required" || status === "applied";
|
|
2159
|
+
const actionable = view !== null && view.updateAvailable && view.canApply && !settled && !busy;
|
|
2160
|
+
const manual = view !== null && (!view.canApply || view.checkFailed) ? view.manualCommand : null;
|
|
2161
|
+
const statusLine = () => {
|
|
2162
|
+
if (status === "checking" || view === null) return t("about.update.checking");
|
|
2163
|
+
if (status === "applying") return t("about.update.applying", { version: target });
|
|
2164
|
+
if (status === "restart-required") return t("about.update.done", { version: view.latestVersion ?? target });
|
|
2165
|
+
if (status === "applied") return t("about.update.applied", { version: view.latestVersion ?? target });
|
|
2166
|
+
if (status === "failed") {
|
|
2167
|
+
if (state.error?.code === "CHECK_FAILED") return t("about.update.checkFailed");
|
|
2168
|
+
return t("about.update.failed", { message: state.error?.message ?? "" });
|
|
2169
|
+
}
|
|
2170
|
+
if (view.checkFailed) return t("about.update.checkFailed");
|
|
2171
|
+
if (view.updateAvailable) return t("about.update.available", { latest: view.latestVersion ?? "", current: view.currentVersion });
|
|
2172
|
+
return t("about.update.current", { version: view.currentVersion });
|
|
2173
|
+
};
|
|
2174
|
+
const actions = [
|
|
2175
|
+
h(Btn, { key: "check", small: true, disabled: busy, onClick: () => void controller.check({ fresh: true }) }, t("about.update.check")),
|
|
2176
|
+
manual === null ? null : h(Btn, { key: "copy", small: true, onClick: copyCommand }, copied ? t("about.update.copied") : t("about.update.copy")),
|
|
2177
|
+
actionable ? confirming ? [
|
|
2178
|
+
h(
|
|
2179
|
+
Btn,
|
|
2180
|
+
{
|
|
2181
|
+
key: "confirm",
|
|
2182
|
+
small: true,
|
|
2183
|
+
variant: "danger",
|
|
2184
|
+
onClick: () => {
|
|
2185
|
+
setConfirming(false);
|
|
2186
|
+
void controller.apply();
|
|
2187
|
+
}
|
|
2188
|
+
},
|
|
2189
|
+
t("about.update.confirmYes")
|
|
2190
|
+
),
|
|
2191
|
+
h(Btn, { key: "cancel", small: true, onClick: () => setConfirming(false) }, t("about.update.cancel"))
|
|
2192
|
+
] : h(Btn, { key: "apply", small: true, variant: "primary", onClick: () => setConfirming(true) }, t("about.update.apply", { version: target })) : null
|
|
2193
|
+
].filter((item) => item !== null);
|
|
2194
|
+
return h(
|
|
2195
|
+
"div",
|
|
2196
|
+
{ className: `dshgt-update${props.bare === true ? " dshgt-update-bare" : ""}` },
|
|
2197
|
+
props.bare === true ? null : h(
|
|
2198
|
+
"div",
|
|
2199
|
+
{ className: "dshgt-update-head" },
|
|
2200
|
+
h("h4", null, t("about.update.title")),
|
|
2201
|
+
h("span", { className: "dshgt-hint" }, t("about.update.hint"))
|
|
2202
|
+
),
|
|
2203
|
+
h("p", { className: `dshgt-update-line${status === "failed" ? " dshgt-update-err" : ""}` }, statusLine()),
|
|
2204
|
+
confirming ? h("p", { className: "dshgt-update-warn" }, t("about.update.confirm", { version: target })) : null,
|
|
2205
|
+
h("div", { className: "dshgt-update-actions" }, actions),
|
|
2206
|
+
manual === null ? null : h(
|
|
2207
|
+
"div",
|
|
2208
|
+
{ className: "dshgt-update-manual" },
|
|
2209
|
+
h("p", { className: "dshgt-update-warn" }, t("about.update.manual", { kind: view?.installKind ?? "unknown" })),
|
|
2210
|
+
h("code", { className: "dshgt-update-cmd" }, manual)
|
|
2211
|
+
)
|
|
2212
|
+
);
|
|
2213
|
+
}
|
|
2214
|
+
|
|
1998
2215
|
// client/digest-core.ts
|
|
1999
2216
|
var DIGEST_WINDOW_DAYS = 30;
|
|
2000
2217
|
var DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
@@ -2328,6 +2545,12 @@ function OverviewView(props) {
|
|
|
2328
2545
|
)
|
|
2329
2546
|
);
|
|
2330
2547
|
})
|
|
2548
|
+
),
|
|
2549
|
+
h(
|
|
2550
|
+
"section",
|
|
2551
|
+
{ className: "dshgt-card dshgt-update-card" },
|
|
2552
|
+
h("div", { className: "dshgt-card-h" }, h("h3", null, t("about.update.title"))),
|
|
2553
|
+
h(PluginUpdateSection, { t, bare: true })
|
|
2331
2554
|
)
|
|
2332
2555
|
)
|
|
2333
2556
|
);
|
|
@@ -3179,117 +3402,10 @@ function metaLine(rendered) {
|
|
|
3179
3402
|
);
|
|
3180
3403
|
}
|
|
3181
3404
|
|
|
3182
|
-
// node_modules/@morehao/dsh-plugin-kit/dist/update-client.js
|
|
3183
|
-
var IDLE = { status: "idle", view: null, error: null };
|
|
3184
|
-
function errorOf(error) {
|
|
3185
|
-
if (error !== null && typeof error === "object") {
|
|
3186
|
-
const value = error;
|
|
3187
|
-
const message = typeof value.message === "string" && value.message !== "" ? value.message : "操作失败";
|
|
3188
|
-
return typeof value.code === "string" ? { code: value.code, message } : { message };
|
|
3189
|
-
}
|
|
3190
|
-
return { message: String(error ?? "操作失败") };
|
|
3191
|
-
}
|
|
3192
|
-
function createUpdateController(options) {
|
|
3193
|
-
const now = options.now ?? Date.now;
|
|
3194
|
-
let state = { ...IDLE };
|
|
3195
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
3196
|
-
let inFlight = null;
|
|
3197
|
-
function emit(next) {
|
|
3198
|
-
state = next;
|
|
3199
|
-
options.onChange?.(state);
|
|
3200
|
-
for (const listener of listeners) listener(state);
|
|
3201
|
-
}
|
|
3202
|
-
async function check(checkOptions = {}) {
|
|
3203
|
-
if (inFlight !== null) return;
|
|
3204
|
-
inFlight = "check";
|
|
3205
|
-
emit({ ...state, status: "checking", error: null });
|
|
3206
|
-
try {
|
|
3207
|
-
const view = await options.request.check(checkOptions);
|
|
3208
|
-
const checkedAt = new Date(now()).toISOString();
|
|
3209
|
-
if (view.checkFailed) {
|
|
3210
|
-
emit({
|
|
3211
|
-
status: "failed",
|
|
3212
|
-
view,
|
|
3213
|
-
error: { code: "CHECK_FAILED", message: "检查更新失败:拿不到 registry 上的最新版本" },
|
|
3214
|
-
checkedAt
|
|
3215
|
-
});
|
|
3216
|
-
} else {
|
|
3217
|
-
emit({ status: view.updateAvailable ? "available" : "current", view, error: null, checkedAt });
|
|
3218
|
-
}
|
|
3219
|
-
} catch (error) {
|
|
3220
|
-
emit({ ...state, status: "failed", error: errorOf(error) });
|
|
3221
|
-
} finally {
|
|
3222
|
-
inFlight = null;
|
|
3223
|
-
}
|
|
3224
|
-
}
|
|
3225
|
-
async function apply2() {
|
|
3226
|
-
if (inFlight !== null) return;
|
|
3227
|
-
const view = state.view;
|
|
3228
|
-
if (view === null || !view.updateAvailable) {
|
|
3229
|
-
emit({ ...state, status: "failed", error: { code: "NO_UPDATE", message: "没有可更新的版本,请先检查更新" } });
|
|
3230
|
-
return;
|
|
3231
|
-
}
|
|
3232
|
-
if (!view.canApply) {
|
|
3233
|
-
emit({
|
|
3234
|
-
...state,
|
|
3235
|
-
status: "failed",
|
|
3236
|
-
error: { code: "NO_APPLY", message: `当前环境不支持一键更新,请执行:${view.manualCommand}` }
|
|
3237
|
-
});
|
|
3238
|
-
return;
|
|
3239
|
-
}
|
|
3240
|
-
inFlight = "apply";
|
|
3241
|
-
emit({ ...state, status: "applying", error: null });
|
|
3242
|
-
try {
|
|
3243
|
-
const result = await options.request.apply();
|
|
3244
|
-
const restartRequired = result.restartRequired ?? result.application !== "applied";
|
|
3245
|
-
emit({ ...state, status: restartRequired ? "restart-required" : "applied", error: null });
|
|
3246
|
-
} catch (error) {
|
|
3247
|
-
emit({ ...state, status: "failed", error: errorOf(error) });
|
|
3248
|
-
} finally {
|
|
3249
|
-
inFlight = null;
|
|
3250
|
-
}
|
|
3251
|
-
}
|
|
3252
|
-
return {
|
|
3253
|
-
getState: () => state,
|
|
3254
|
-
subscribe: (listener) => {
|
|
3255
|
-
listeners.add(listener);
|
|
3256
|
-
return () => {
|
|
3257
|
-
listeners.delete(listener);
|
|
3258
|
-
};
|
|
3259
|
-
},
|
|
3260
|
-
check,
|
|
3261
|
-
apply: apply2,
|
|
3262
|
-
reset: () => emit({ ...IDLE })
|
|
3263
|
-
};
|
|
3264
|
-
}
|
|
3265
|
-
|
|
3266
3405
|
// client/about.ts
|
|
3267
3406
|
function AboutDialog(props) {
|
|
3268
3407
|
const { t, state } = props;
|
|
3269
3408
|
const [copied, setCopied] = import_react.default.useState(false);
|
|
3270
|
-
const [copiedCommand, setCopiedCommand] = import_react.default.useState(false);
|
|
3271
|
-
const [confirming, setConfirming] = import_react.default.useState(false);
|
|
3272
|
-
const controllerRef = import_react.default.useRef(null);
|
|
3273
|
-
if (controllerRef.current === null) {
|
|
3274
|
-
controllerRef.current = createUpdateController({
|
|
3275
|
-
request: {
|
|
3276
|
-
check: (options) => getPluginUpdate(options.fresh === true ? { fresh: true } : {}),
|
|
3277
|
-
apply: () => applyPluginUpdate()
|
|
3278
|
-
}
|
|
3279
|
-
});
|
|
3280
|
-
}
|
|
3281
|
-
const controller = controllerRef.current;
|
|
3282
|
-
const [updateState, setUpdateState] = import_react.default.useState(() => controller.getState());
|
|
3283
|
-
import_react.default.useEffect(() => {
|
|
3284
|
-
const unsubscribe = controller.subscribe(setUpdateState);
|
|
3285
|
-
if (controller.getState().status === "idle") void controller.check();
|
|
3286
|
-
return unsubscribe;
|
|
3287
|
-
}, [controller]);
|
|
3288
|
-
import_react.default.useEffect(() => {
|
|
3289
|
-
if (updateState.status === "applying" || updateState.status === "restart-required" || updateState.status === "applied") {
|
|
3290
|
-
setConfirming(false);
|
|
3291
|
-
}
|
|
3292
|
-
}, [updateState.status]);
|
|
3293
3409
|
const plugin = state.plugin;
|
|
3294
3410
|
const fullName = `${plugin.name}@v${plugin.version}`;
|
|
3295
3411
|
const repository = plugin.repository;
|
|
@@ -3309,59 +3425,6 @@ function AboutDialog(props) {
|
|
|
3309
3425
|
setTimeout(() => setCopied(false), 1600);
|
|
3310
3426
|
});
|
|
3311
3427
|
};
|
|
3312
|
-
const copyCommand = () => {
|
|
3313
|
-
const command = updateState.view?.manualCommand;
|
|
3314
|
-
if (command === void 0) return;
|
|
3315
|
-
void copyText(command).then((ok) => {
|
|
3316
|
-
if (!ok) return;
|
|
3317
|
-
setCopiedCommand(true);
|
|
3318
|
-
setTimeout(() => setCopiedCommand(false), 1600);
|
|
3319
|
-
});
|
|
3320
|
-
};
|
|
3321
|
-
const view = updateState.view;
|
|
3322
|
-
const status = updateState.status;
|
|
3323
|
-
const target = view !== null && view.updateAvailable ? view.latestVersion ?? "" : "";
|
|
3324
|
-
const busy = status === "checking" || status === "applying";
|
|
3325
|
-
const settled = status === "restart-required" || status === "applied";
|
|
3326
|
-
const actionable = view !== null && view.updateAvailable && view.canApply && !settled && !busy;
|
|
3327
|
-
const manual = view !== null && (!view.canApply || view.checkFailed) ? view.manualCommand : null;
|
|
3328
|
-
const statusLine = () => {
|
|
3329
|
-
if (status === "checking" || view === null) return t("about.update.checking");
|
|
3330
|
-
if (status === "applying") return t("about.update.applying", { version: target });
|
|
3331
|
-
if (status === "restart-required") return t("about.update.done", { version: view.latestVersion ?? target });
|
|
3332
|
-
if (status === "applied") return t("about.update.applied", { version: view.latestVersion ?? target });
|
|
3333
|
-
if (status === "failed") {
|
|
3334
|
-
if (updateState.error?.code === "CHECK_FAILED") return t("about.update.checkFailed");
|
|
3335
|
-
return t("about.update.failed", { message: updateState.error?.message ?? "" });
|
|
3336
|
-
}
|
|
3337
|
-
if (view.checkFailed) return t("about.update.checkFailed");
|
|
3338
|
-
if (view.updateAvailable) return t("about.update.available", { latest: view.latestVersion ?? "", current: view.currentVersion });
|
|
3339
|
-
return t("about.update.current", { version: view.currentVersion });
|
|
3340
|
-
};
|
|
3341
|
-
const updateActions = [
|
|
3342
|
-
h(Btn, { key: "check", small: true, disabled: busy, onClick: () => void controller.check({ fresh: true }) }, t("about.update.check")),
|
|
3343
|
-
manual === null ? null : h(
|
|
3344
|
-
Btn,
|
|
3345
|
-
{ key: "copy-command", small: true, onClick: copyCommand },
|
|
3346
|
-
copiedCommand ? t("about.update.copied") : t("about.update.copy")
|
|
3347
|
-
),
|
|
3348
|
-
actionable ? confirming ? [
|
|
3349
|
-
h(
|
|
3350
|
-
Btn,
|
|
3351
|
-
{
|
|
3352
|
-
key: "confirm",
|
|
3353
|
-
small: true,
|
|
3354
|
-
variant: "danger",
|
|
3355
|
-
onClick: () => {
|
|
3356
|
-
setConfirming(false);
|
|
3357
|
-
void controller.apply();
|
|
3358
|
-
}
|
|
3359
|
-
},
|
|
3360
|
-
t("about.update.confirmYes")
|
|
3361
|
-
),
|
|
3362
|
-
h(Btn, { key: "cancel", small: true, onClick: () => setConfirming(false) }, t("about.update.cancel"))
|
|
3363
|
-
] : h(Btn, { key: "apply", small: true, variant: "primary", onClick: () => setConfirming(true) }, t("about.update.apply", { version: target })) : null
|
|
3364
|
-
].filter((item) => item !== null);
|
|
3365
3428
|
const dialog = h(
|
|
3366
3429
|
"div",
|
|
3367
3430
|
{ className: "dshgt-modal-mask", onClick: (event) => {
|
|
@@ -3393,25 +3456,7 @@ function AboutDialog(props) {
|
|
|
3393
3456
|
[t("about.env"), t("about.env.value")]
|
|
3394
3457
|
]
|
|
3395
3458
|
}),
|
|
3396
|
-
h(
|
|
3397
|
-
"div",
|
|
3398
|
-
{ className: "dshgt-update" },
|
|
3399
|
-
h(
|
|
3400
|
-
"div",
|
|
3401
|
-
{ className: "dshgt-update-head" },
|
|
3402
|
-
h("h4", null, t("about.update.title")),
|
|
3403
|
-
h("span", { className: "dshgt-hint" }, t("about.update.hint"))
|
|
3404
|
-
),
|
|
3405
|
-
h("p", { className: `dshgt-update-line${status === "failed" ? " dshgt-update-err" : ""}` }, statusLine()),
|
|
3406
|
-
confirming ? h("p", { className: "dshgt-update-warn" }, t("about.update.confirm", { version: target })) : null,
|
|
3407
|
-
h("div", { className: "dshgt-update-actions" }, updateActions),
|
|
3408
|
-
manual === null ? null : h(
|
|
3409
|
-
"div",
|
|
3410
|
-
{ className: "dshgt-update-manual" },
|
|
3411
|
-
h("p", { className: "dshgt-update-warn" }, t("about.update.manual", { kind: view?.installKind ?? "unknown" })),
|
|
3412
|
-
h("code", { className: "dshgt-update-cmd" }, manual)
|
|
3413
|
-
)
|
|
3414
|
-
)
|
|
3459
|
+
h(PluginUpdateSection, { t })
|
|
3415
3460
|
)
|
|
3416
3461
|
);
|
|
3417
3462
|
return h("div", { className: "dshgt-root" }, dialog);
|
|
@@ -3659,7 +3704,8 @@ function apply(ctx) {
|
|
|
3659
3704
|
onClick: () => setAboutOpen(true),
|
|
3660
3705
|
title: plugin === void 0 ? "" : fullName
|
|
3661
3706
|
},
|
|
3662
|
-
plugin?.name ?? t("section.title")
|
|
3707
|
+
plugin?.name ?? t("section.title"),
|
|
3708
|
+
h(UpdateDot, { t })
|
|
3663
3709
|
),
|
|
3664
3710
|
h(
|
|
3665
3711
|
"div",
|
|
@@ -9,9 +9,11 @@ var config_exports = {};
|
|
|
9
9
|
__export(config_exports, {
|
|
10
10
|
CORE_PROVIDERS: () => CORE_PROVIDERS,
|
|
11
11
|
DEFAULT_SETTINGS: () => DEFAULT_SETTINGS,
|
|
12
|
+
PERIODS: () => PERIODS,
|
|
12
13
|
PROVIDER_BASE_URLS: () => PROVIDER_BASE_URLS,
|
|
13
14
|
SETTINGS_SECTIONS: () => SETTINGS_SECTIONS,
|
|
14
15
|
effectiveBaseURL: () => effectiveBaseURL,
|
|
16
|
+
isPeriod: () => isPeriod,
|
|
15
17
|
isValidTimezone: () => isValidTimezone,
|
|
16
18
|
mergeSettingsSection: () => mergeSettingsSection,
|
|
17
19
|
normalizeSettings: () => normalizeSettings,
|
|
@@ -21,6 +23,10 @@ __export(config_exports, {
|
|
|
21
23
|
validateSettings: () => validateSettings
|
|
22
24
|
});
|
|
23
25
|
import path from "path";
|
|
26
|
+
var PERIODS = ["daily", "weekly", "monthly"];
|
|
27
|
+
function isPeriod(value) {
|
|
28
|
+
return value === "daily" || value === "weekly" || value === "monthly";
|
|
29
|
+
}
|
|
24
30
|
var DEFAULT_SETTINGS = {
|
|
25
31
|
model: {
|
|
26
32
|
provider: "deepseek",
|
|
@@ -50,7 +56,7 @@ var DEFAULT_SETTINGS = {
|
|
|
50
56
|
github: { authMode: "gh", tokenRef: "GITHUB_TOKEN" },
|
|
51
57
|
trending: { since: "daily", languages: ["all", "typescript", "python", "go", "rust"], topN: 25 },
|
|
52
58
|
output: { dir: "trending" },
|
|
53
|
-
schedule: { enabled: true, time: "08:30:00", timezone: "Asia/Shanghai" },
|
|
59
|
+
schedule: { enabled: true, time: "08:30:00", timezone: "Asia/Shanghai", periods: ["daily"] },
|
|
54
60
|
corePath: "",
|
|
55
61
|
coreAutoDownload: true
|
|
56
62
|
};
|
|
@@ -111,6 +117,11 @@ function strArray(value, fallback) {
|
|
|
111
117
|
const items = value.filter((item) => typeof item === "string" && item.trim() !== "");
|
|
112
118
|
return items.length > 0 ? items.map((item) => item.trim()) : fallback;
|
|
113
119
|
}
|
|
120
|
+
function periodList(value, fallback) {
|
|
121
|
+
if (value === void 0 || !Array.isArray(value)) return [...fallback];
|
|
122
|
+
const set = new Set(value.filter(isPeriod));
|
|
123
|
+
return PERIODS.filter((period) => set.has(period));
|
|
124
|
+
}
|
|
114
125
|
function normalizeSettings(input) {
|
|
115
126
|
const raw = typeof input === "object" && input !== null ? input : {};
|
|
116
127
|
const model = raw.model ?? {};
|
|
@@ -155,7 +166,7 @@ function normalizeSettings(input) {
|
|
|
155
166
|
tokenRef: str(github.tokenRef, DEFAULT_SETTINGS.github.tokenRef).trim() || DEFAULT_SETTINGS.github.tokenRef
|
|
156
167
|
},
|
|
157
168
|
trending: {
|
|
158
|
-
since: since
|
|
169
|
+
since: isPeriod(since) ? since : "daily",
|
|
159
170
|
languages: strArray(trending.languages, DEFAULT_SETTINGS.trending.languages),
|
|
160
171
|
topN: Math.round(num(trending.topN, DEFAULT_SETTINGS.trending.topN))
|
|
161
172
|
},
|
|
@@ -165,7 +176,8 @@ function normalizeSettings(input) {
|
|
|
165
176
|
schedule: {
|
|
166
177
|
enabled: bool(schedule.enabled, DEFAULT_SETTINGS.schedule.enabled),
|
|
167
178
|
time: str(schedule.time, DEFAULT_SETTINGS.schedule.time),
|
|
168
|
-
timezone: str(schedule.timezone, DEFAULT_SETTINGS.schedule.timezone)
|
|
179
|
+
timezone: str(schedule.timezone, DEFAULT_SETTINGS.schedule.timezone),
|
|
180
|
+
periods: periodList(schedule.periods, DEFAULT_SETTINGS.schedule.periods)
|
|
169
181
|
},
|
|
170
182
|
corePath: str(raw.corePath, DEFAULT_SETTINGS.corePath).trim(),
|
|
171
183
|
coreAutoDownload: bool(raw.coreAutoDownload, DEFAULT_SETTINGS.coreAutoDownload)
|
|
@@ -231,12 +243,15 @@ function validateSettings(settings, providers = CORE_PROVIDERS) {
|
|
|
231
243
|
if (!isValidTimezone(schedule.timezone)) {
|
|
232
244
|
problems.push({ field: "schedule.timezone", code: "CONFIG_INVALID", message: "\u65F6\u533A\u5FC5\u987B\u662F IANA \u540D\u79F0\uFF08\u5982 Asia/Shanghai\uFF09" });
|
|
233
245
|
}
|
|
246
|
+
if (schedule.enabled && schedule.periods.length === 0) {
|
|
247
|
+
problems.push({ field: "schedule.periods", code: "CONFIG_INVALID", message: "\u5B9A\u65F6\u8FD0\u884C\u5F00\u542F\u65F6\u81F3\u5C11\u8981\u9009\u62E9\u4E00\u4E2A\u4EA7\u51FA\u5468\u671F\uFF08\u65E5/\u5468/\u6708\uFF09" });
|
|
248
|
+
}
|
|
234
249
|
return problems;
|
|
235
250
|
}
|
|
236
251
|
function effectiveBaseURL(model) {
|
|
237
252
|
return model.baseURL !== "" ? model.baseURL : PROVIDER_BASE_URLS[model.provider] ?? "";
|
|
238
253
|
}
|
|
239
|
-
function toCoreConfig(settings, apiKey, baseURLOverride) {
|
|
254
|
+
function toCoreConfig(settings, apiKey, baseURLOverride, period) {
|
|
240
255
|
const config = {
|
|
241
256
|
model: {
|
|
242
257
|
provider: settings.model.provider,
|
|
@@ -262,7 +277,7 @@ function toCoreConfig(settings, apiKey, baseURLOverride) {
|
|
|
262
277
|
},
|
|
263
278
|
trending: {
|
|
264
279
|
languages: settings.trending.languages,
|
|
265
|
-
since: settings.trending.since,
|
|
280
|
+
since: period ?? settings.trending.since,
|
|
266
281
|
perLanguage: settings.trending.topN
|
|
267
282
|
},
|
|
268
283
|
output: {
|
|
@@ -1339,6 +1354,7 @@ function buildCoreArgs(channelPath, options) {
|
|
|
1339
1354
|
const args = ["run", "--config", channelPath, "--json"];
|
|
1340
1355
|
if (options.dryRun) args.push("--dry-run");
|
|
1341
1356
|
if (options.date !== void 0 && options.date !== "") args.push("--date", options.date);
|
|
1357
|
+
if (options.period !== void 0) args.push("--period", options.period);
|
|
1342
1358
|
return args;
|
|
1343
1359
|
}
|
|
1344
1360
|
async function runCore(options) {
|
|
@@ -1346,10 +1362,10 @@ async function runCore(options) {
|
|
|
1346
1362
|
const startedAt = nowIso();
|
|
1347
1363
|
const started = Date.now();
|
|
1348
1364
|
const channel = await openConfigChannel();
|
|
1349
|
-
const yaml = `${toYaml(toCoreConfig(options.settings, options.apiKey, options.baseURLOverride))}
|
|
1365
|
+
const yaml = `${toYaml(toCoreConfig(options.settings, options.apiKey, options.baseURLOverride, options.period))}
|
|
1350
1366
|
`;
|
|
1351
1367
|
const secrets = [options.apiKey, options.token, ...options.secrets ?? []];
|
|
1352
|
-
const child = spawn2(options.core.path, buildCoreArgs(channel.path, { dryRun: options.dryRun, date: options.date }), {
|
|
1368
|
+
const child = spawn2(options.core.path, buildCoreArgs(channel.path, { dryRun: options.dryRun, date: options.date, period: options.period }), {
|
|
1353
1369
|
env: sanitizedEnv(process.env, {
|
|
1354
1370
|
GITHUB_TOKEN: options.token,
|
|
1355
1371
|
GH_TOKEN: options.token,
|
|
@@ -1985,8 +2001,11 @@ function authorized(request, token) {
|
|
|
1985
2001
|
var schedule_exports = {};
|
|
1986
2002
|
__export(schedule_exports, {
|
|
1987
2003
|
Scheduler: () => Scheduler,
|
|
2004
|
+
daysInMonth: () => daysInMonth,
|
|
1988
2005
|
isDue: () => isDue,
|
|
2006
|
+
isPeriodTriggerDay: () => isPeriodTriggerDay,
|
|
1989
2007
|
nextOccurrence: () => nextOccurrence,
|
|
2008
|
+
nextOccurrenceFor: () => nextOccurrenceFor,
|
|
1990
2009
|
parseClock: () => parseClock,
|
|
1991
2010
|
zonedDateString: () => zonedDateString,
|
|
1992
2011
|
zonedTimeToUtc: () => zonedTimeToUtc
|
|
@@ -2066,6 +2085,35 @@ function isDue(now, schedule) {
|
|
|
2066
2085
|
const late = nowMinutes - targetMinutes;
|
|
2067
2086
|
return late >= 0 && late <= CATCH_UP_MINUTES;
|
|
2068
2087
|
}
|
|
2088
|
+
function daysInMonth(year, month) {
|
|
2089
|
+
return new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
2090
|
+
}
|
|
2091
|
+
function isPeriodTriggerDate(period, year, month, day) {
|
|
2092
|
+
if (period === "weekly") {
|
|
2093
|
+
return new Date(Date.UTC(year, month - 1, day)).getUTCDay() === 0;
|
|
2094
|
+
}
|
|
2095
|
+
if (period === "monthly") return day === daysInMonth(year, month);
|
|
2096
|
+
return true;
|
|
2097
|
+
}
|
|
2098
|
+
function isPeriodTriggerDay(period, now, timezone) {
|
|
2099
|
+
const parts = partsOf(now, timezone);
|
|
2100
|
+
return isPeriodTriggerDate(period, parts.year, parts.month, parts.day);
|
|
2101
|
+
}
|
|
2102
|
+
function nextOccurrenceFor(period, now, schedule) {
|
|
2103
|
+
const { hour, minute, second } = parseClock(schedule.time);
|
|
2104
|
+
const today = partsOf(now, schedule.timezone);
|
|
2105
|
+
const start = Date.UTC(today.year, today.month - 1, today.day);
|
|
2106
|
+
for (let i = 0; i < 400; i += 1) {
|
|
2107
|
+
const d = new Date(start + i * 864e5);
|
|
2108
|
+
const year = d.getUTCFullYear();
|
|
2109
|
+
const month = d.getUTCMonth() + 1;
|
|
2110
|
+
const day = d.getUTCDate();
|
|
2111
|
+
if (!isPeriodTriggerDate(period, year, month, day)) continue;
|
|
2112
|
+
const candidate = zonedTimeToUtc(year, month, day, hour, minute, second, schedule.timezone);
|
|
2113
|
+
if (candidate.getTime() > now.getTime()) return candidate;
|
|
2114
|
+
}
|
|
2115
|
+
return nextOccurrence(now, schedule);
|
|
2116
|
+
}
|
|
2069
2117
|
var Scheduler = class {
|
|
2070
2118
|
deps;
|
|
2071
2119
|
timerHandle;
|
|
@@ -2104,41 +2152,63 @@ var Scheduler = class {
|
|
|
2104
2152
|
if (!schedule.enabled) return "disabled";
|
|
2105
2153
|
if (!isDue(now, schedule)) return "skipped";
|
|
2106
2154
|
const today = zonedDateString(now, schedule.timezone);
|
|
2107
|
-
const state = this.deps.store.readState();
|
|
2108
|
-
const lastSuccess = state.lastSuccessAt === void 0 ? void 0 : zonedDateString(new Date(state.lastSuccessAt), schedule.timezone);
|
|
2109
|
-
if (lastSuccess === today) return "skipped";
|
|
2110
|
-
const attempt = this.attempts.get(today);
|
|
2111
|
-
if (attempt !== void 0) {
|
|
2112
|
-
if (attempt.count >= MAX_ATTEMPTS_PER_DAY) return "skipped";
|
|
2113
|
-
if (now.getTime() - attempt.lastAt < RETRY_GAP_MS) return "skipped";
|
|
2114
|
-
}
|
|
2115
|
-
if (this.deps.isRunning()) return "busy";
|
|
2116
2155
|
for (const key of this.attempts.keys()) {
|
|
2117
|
-
if (key
|
|
2156
|
+
if (!key.endsWith(`:${today}`)) this.attempts.delete(key);
|
|
2157
|
+
}
|
|
2158
|
+
const periods = schedule.periods.length > 0 ? schedule.periods : ["daily"];
|
|
2159
|
+
let ran = false;
|
|
2160
|
+
for (const period of periods) {
|
|
2161
|
+
if (!isPeriodTriggerDay(period, now, schedule.timezone)) continue;
|
|
2162
|
+
const state = this.deps.store.readState();
|
|
2163
|
+
if (this.succeededToday(state, period, today, schedule.timezone)) continue;
|
|
2164
|
+
const key = `${period}:${today}`;
|
|
2165
|
+
const attempt = this.attempts.get(key);
|
|
2166
|
+
if (attempt !== void 0) {
|
|
2167
|
+
if (attempt.count >= MAX_ATTEMPTS_PER_DAY) continue;
|
|
2168
|
+
if (now.getTime() - attempt.lastAt < RETRY_GAP_MS) continue;
|
|
2169
|
+
}
|
|
2170
|
+
if (this.deps.isRunning()) return "busy";
|
|
2171
|
+
this.attempts.set(key, { count: (attempt?.count ?? 0) + 1, lastAt: now.getTime() });
|
|
2172
|
+
try {
|
|
2173
|
+
await this.deps.run("schedule", period);
|
|
2174
|
+
ran = true;
|
|
2175
|
+
} catch (error) {
|
|
2176
|
+
this.deps.onError?.(error);
|
|
2177
|
+
}
|
|
2118
2178
|
}
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2179
|
+
return ran ? "ran" : "skipped";
|
|
2180
|
+
}
|
|
2181
|
+
/** 该周期今天是否已经成功过(含无变更跳过)。 */
|
|
2182
|
+
succeededToday(state, period, today, timezone) {
|
|
2183
|
+
const stamp = state.periodSuccess?.[period];
|
|
2184
|
+
if (stamp !== void 0 && zonedDateString(new Date(stamp), timezone) === today) return true;
|
|
2185
|
+
if (period === "daily" && state.lastSuccessAt !== void 0 && zonedDateString(new Date(state.lastSuccessAt), timezone) === today) {
|
|
2186
|
+
return true;
|
|
2126
2187
|
}
|
|
2188
|
+
return false;
|
|
2127
2189
|
}
|
|
2128
2190
|
readSchedule() {
|
|
2129
2191
|
const settings = this.deps.store.readSettings();
|
|
2130
2192
|
return {
|
|
2131
2193
|
enabled: settings.schedule.enabled,
|
|
2132
2194
|
time: settings.schedule.time,
|
|
2133
|
-
timezone: settings.schedule.timezone
|
|
2195
|
+
timezone: settings.schedule.timezone,
|
|
2196
|
+
periods: settings.schedule.periods
|
|
2134
2197
|
};
|
|
2135
2198
|
}
|
|
2136
|
-
/**
|
|
2199
|
+
/** 下一次运行(取所有已启用周期里最近的一次;禁用时为 undefined)。 */
|
|
2137
2200
|
next() {
|
|
2138
2201
|
const schedule = this.readSchedule();
|
|
2139
2202
|
if (!schedule.enabled) return void 0;
|
|
2203
|
+
const periods = schedule.periods.length > 0 ? schedule.periods : ["daily"];
|
|
2140
2204
|
try {
|
|
2141
|
-
|
|
2205
|
+
const now = /* @__PURE__ */ new Date();
|
|
2206
|
+
let earliest;
|
|
2207
|
+
for (const period of periods) {
|
|
2208
|
+
const occurrence = nextOccurrenceFor(period, now, schedule);
|
|
2209
|
+
if (earliest === void 0 || occurrence.getTime() < earliest.getTime()) earliest = occurrence;
|
|
2210
|
+
}
|
|
2211
|
+
return earliest;
|
|
2142
2212
|
} catch {
|
|
2143
2213
|
return void 0;
|
|
2144
2214
|
}
|
|
@@ -2150,6 +2220,7 @@ var Scheduler = class {
|
|
|
2150
2220
|
enabled: schedule.enabled,
|
|
2151
2221
|
time: schedule.time,
|
|
2152
2222
|
timezone: schedule.timezone,
|
|
2223
|
+
periods: schedule.periods,
|
|
2153
2224
|
...next === void 0 ? {} : { nextRunAt: next.toISOString() },
|
|
2154
2225
|
...state.lastRunAt === void 0 ? {} : { lastRunDate: zonedDateString(new Date(state.lastRunAt), schedule.timezone) },
|
|
2155
2226
|
catchUpMinutes: CATCH_UP_MINUTES,
|
|
@@ -2161,7 +2232,9 @@ var Scheduler = class {
|
|
|
2161
2232
|
// src/host/runner.ts
|
|
2162
2233
|
var runner_exports = {};
|
|
2163
2234
|
__export(runner_exports, {
|
|
2164
|
-
Runner: () => Runner
|
|
2235
|
+
Runner: () => Runner,
|
|
2236
|
+
isoWeekName: () => isoWeekName,
|
|
2237
|
+
periodFileBase: () => periodFileBase
|
|
2165
2238
|
});
|
|
2166
2239
|
import { createHash as createHash2 } from "crypto";
|
|
2167
2240
|
import { existsSync as existsSync4, readFileSync as readFileSync3, realpathSync, statSync as statSync5 } from "fs";
|
|
@@ -2195,6 +2268,25 @@ function normalizeSha256(value) {
|
|
|
2195
2268
|
const hex = value.trim().replace(/^sha256:/i, "").toLowerCase();
|
|
2196
2269
|
return /^[0-9a-f]{64}$/.test(hex) ? hex : void 0;
|
|
2197
2270
|
}
|
|
2271
|
+
function isoWeekName(date) {
|
|
2272
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
|
|
2273
|
+
if (match === null) return date;
|
|
2274
|
+
const d = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3])));
|
|
2275
|
+
if (Number.isNaN(d.getTime())) return date;
|
|
2276
|
+
const dayIdx = (d.getUTCDay() + 6) % 7;
|
|
2277
|
+
d.setUTCDate(d.getUTCDate() - dayIdx + 3);
|
|
2278
|
+
const isoYear = d.getUTCFullYear();
|
|
2279
|
+
const firstThursday = new Date(Date.UTC(isoYear, 0, 4));
|
|
2280
|
+
const firstIdx = (firstThursday.getUTCDay() + 6) % 7;
|
|
2281
|
+
firstThursday.setUTCDate(firstThursday.getUTCDate() - firstIdx + 3);
|
|
2282
|
+
const week = 1 + Math.round((d.getTime() - firstThursday.getTime()) / (7 * 864e5));
|
|
2283
|
+
return `${isoYear}-W${String(week).padStart(2, "0")}`;
|
|
2284
|
+
}
|
|
2285
|
+
function periodFileBase(period, date) {
|
|
2286
|
+
if (period === "monthly") return date.slice(0, 7);
|
|
2287
|
+
if (period === "weekly") return isoWeekName(date);
|
|
2288
|
+
return date;
|
|
2289
|
+
}
|
|
2198
2290
|
var Runner = class {
|
|
2199
2291
|
active;
|
|
2200
2292
|
deps;
|
|
@@ -2226,6 +2318,7 @@ var Runner = class {
|
|
|
2226
2318
|
if (repoIssue !== void 0) throw new IpcError(repoIssue.code, repoIssue.message);
|
|
2227
2319
|
const dryRun = options.dryRun === true;
|
|
2228
2320
|
const date = options.date !== void 0 && options.date !== "" ? options.date : runDate(settings);
|
|
2321
|
+
const period = options.period ?? settings.trending.since;
|
|
2229
2322
|
const id = newRunId();
|
|
2230
2323
|
const controller = new AbortController();
|
|
2231
2324
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -2282,6 +2375,7 @@ var Runner = class {
|
|
|
2282
2375
|
apiKey,
|
|
2283
2376
|
...github.token === void 0 ? {} : { token: github.token },
|
|
2284
2377
|
date,
|
|
2378
|
+
period,
|
|
2285
2379
|
dryRun,
|
|
2286
2380
|
trigger: options.trigger === "schedule" ? "schedule" : "manual",
|
|
2287
2381
|
...baseURLOverride === void 0 ? {} : { baseURLOverride },
|
|
@@ -2295,6 +2389,7 @@ var Runner = class {
|
|
|
2295
2389
|
outcome,
|
|
2296
2390
|
dryRun,
|
|
2297
2391
|
date,
|
|
2392
|
+
period,
|
|
2298
2393
|
logFile,
|
|
2299
2394
|
channel: result.channelKind,
|
|
2300
2395
|
proxy: proxy !== void 0,
|
|
@@ -2304,6 +2399,7 @@ var Runner = class {
|
|
|
2304
2399
|
const preview = this.capturePreview({
|
|
2305
2400
|
id,
|
|
2306
2401
|
date,
|
|
2402
|
+
period,
|
|
2307
2403
|
settings,
|
|
2308
2404
|
outcome,
|
|
2309
2405
|
secrets: [apiKey, github.token]
|
|
@@ -2318,6 +2414,7 @@ var Runner = class {
|
|
|
2318
2414
|
error: cancelled && !(error instanceof IpcError && error.code === "CORE_CANCELLED") ? new IpcError("CORE_CANCELLED", "\u8FD0\u884C\u5DF2\u53D6\u6D88\uFF1A\u6838\u5FC3\u8FDB\u7A0B\u5DF2\u7EC8\u6B62\uFF0C\u672A\u63D0\u4EA4\u4EFB\u4F55\u72B6\u6001") : error,
|
|
2319
2415
|
dryRun,
|
|
2320
2416
|
date,
|
|
2417
|
+
period,
|
|
2321
2418
|
logFile,
|
|
2322
2419
|
durationMs: Date.now() - startedMs
|
|
2323
2420
|
});
|
|
@@ -2353,6 +2450,7 @@ var Runner = class {
|
|
|
2353
2450
|
skipped: outcome.skipped,
|
|
2354
2451
|
dryRun: input.dryRun,
|
|
2355
2452
|
...input.date === void 0 ? {} : { date: input.date },
|
|
2453
|
+
...input.period === void 0 ? {} : { period: input.period },
|
|
2356
2454
|
durationMs: outcome.durationMs,
|
|
2357
2455
|
coreVersion: outcome.coreVersion,
|
|
2358
2456
|
files: outcome.files.length,
|
|
@@ -2379,6 +2477,7 @@ var Runner = class {
|
|
|
2379
2477
|
skipped: false,
|
|
2380
2478
|
dryRun: input.dryRun,
|
|
2381
2479
|
...input.date === void 0 ? {} : { date: input.date },
|
|
2480
|
+
...input.period === void 0 ? {} : { period: input.period },
|
|
2382
2481
|
durationMs: input.durationMs ?? 0,
|
|
2383
2482
|
coreVersion: "",
|
|
2384
2483
|
files: 0,
|
|
@@ -2396,7 +2495,7 @@ var Runner = class {
|
|
|
2396
2495
|
* - 任何一步失败都只记日志,返回 undefined;运行结果不受影响。
|
|
2397
2496
|
*/
|
|
2398
2497
|
capturePreview(input) {
|
|
2399
|
-
const { outcome, settings, date, id } = input;
|
|
2498
|
+
const { outcome, settings, date, period, id } = input;
|
|
2400
2499
|
if (!outcome.ok || outcome.dryRun || outcome.skipped || date === void 0) return void 0;
|
|
2401
2500
|
const skip = (reason, extra = {}) => {
|
|
2402
2501
|
this.deps.logger?.("preview.skip", { id, reason, ...extra });
|
|
@@ -2404,12 +2503,14 @@ var Runner = class {
|
|
|
2404
2503
|
};
|
|
2405
2504
|
const markdown = outcome.files.filter((file) => {
|
|
2406
2505
|
const ext = path8.extname(file.path).toLowerCase();
|
|
2407
|
-
|
|
2506
|
+
const base = path8.basename(file.path).toLowerCase();
|
|
2507
|
+
return ext === ".md" && base !== "readme.md" && base !== "index.md";
|
|
2408
2508
|
});
|
|
2409
2509
|
if (markdown.length === 0) return skip("no-markdown");
|
|
2410
|
-
const
|
|
2411
|
-
const
|
|
2412
|
-
|
|
2510
|
+
const expected = period === void 0 ? `${date}.md` : `${periodFileBase(period, date)}.md`;
|
|
2511
|
+
const named = markdown.filter((file) => path8.basename(file.path) === expected);
|
|
2512
|
+
const chosen = named.length > 0 ? named[0] : markdown.length === 1 ? markdown[0] : void 0;
|
|
2513
|
+
if (chosen === void 0) return skip("ambiguous", { candidates: markdown.length, expected, period: period ?? null });
|
|
2413
2514
|
const repoPath = settings.repo.path;
|
|
2414
2515
|
const resolved = path8.resolve(repoPath, chosen.path);
|
|
2415
2516
|
if (!isInside(repoPath, resolved)) return skip("outside-repo", { path: chosen.path });
|
|
@@ -2467,6 +2568,7 @@ var Runner = class {
|
|
|
2467
2568
|
durationMs: summary.durationMs,
|
|
2468
2569
|
files: summary.files,
|
|
2469
2570
|
...summary.date === void 0 ? {} : { date: summary.date },
|
|
2571
|
+
...summary.period === void 0 ? {} : { period: summary.period },
|
|
2470
2572
|
...summary.contentHash === void 0 ? {} : { contentHash: summary.contentHash },
|
|
2471
2573
|
...summary.branch === void 0 ? {} : { branch: summary.branch },
|
|
2472
2574
|
...summary.commit === void 0 ? {} : { commit: summary.commit },
|
|
@@ -2479,9 +2581,12 @@ var Runner = class {
|
|
|
2479
2581
|
logFile: summary.logFile
|
|
2480
2582
|
};
|
|
2481
2583
|
const state = store.readState();
|
|
2584
|
+
const periodSuccess = { ...state.periodSuccess ?? {} };
|
|
2585
|
+
if (summary.ok && summary.period !== void 0) periodSuccess[summary.period] = record.at;
|
|
2482
2586
|
const next = {
|
|
2483
2587
|
lastRunAt: record.at,
|
|
2484
2588
|
...summary.ok ? { lastSuccessAt: record.at } : {},
|
|
2589
|
+
...summary.ok && summary.period !== void 0 ? { periodSuccess } : {},
|
|
2485
2590
|
...summary.ok && summary.prUrl !== void 0 ? { pendingPr: { url: summary.prUrl, createdAt: record.at, ...summary.date === void 0 ? {} : { date: summary.date } } } : {},
|
|
2486
2591
|
...summary.ok && !summary.skipped && summary.contentHash !== void 0 ? {
|
|
2487
2592
|
lastDigest: {
|
|
@@ -3224,6 +3329,7 @@ async function dispatch(method, pathname, url, request, deps) {
|
|
|
3224
3329
|
const summary = await deps.runner.run({
|
|
3225
3330
|
dryRun: body.dryRun === true,
|
|
3226
3331
|
...typeof body.date === "string" && body.date !== "" ? { date: body.date } : {},
|
|
3332
|
+
...isPeriod(body.period) ? { period: body.period } : {},
|
|
3227
3333
|
trigger: "manual"
|
|
3228
3334
|
});
|
|
3229
3335
|
return { summary };
|
|
@@ -3496,10 +3602,11 @@ function describeRecord(record) {
|
|
|
3496
3602
|
function createTrendingRunTool(runner) {
|
|
3497
3603
|
return {
|
|
3498
3604
|
name: "trending_run",
|
|
3499
|
-
description: "\u6267\u884C\u4E00\u6B21 GitHub \u8D8B\u52BF\
|
|
3605
|
+
description: "\u6267\u884C\u4E00\u6B21 GitHub \u8D8B\u52BF\u699C\u5355\uFF1A\u5B9A\u4F4D\u5E76\u63E1\u624B\u672C\u5730\u6838\u5FC3\uFF0Cspawn \u4E00\u6B21 run\uFF0C\u8FD4\u56DE\u7ED3\u6784\u5316\u6458\u8981\uFF08\u6587\u4EF6\u6570\u3001commit\u3001PR\u3001\u9519\u8BEF\u7801\uFF09\u3002dryRun=true \u53EA\u6293\u53D6\u4E0E\u6E32\u67D3\u3001\u4E0D\u5199\u6587\u4EF6\u4E5F\u4E0D\u63D0\u4EA4\u3002\u540C\u65E5\u540C\u5185\u5BB9\u91CD\u8DD1\u4F1A\u8FD4\u56DE skipped=true\uFF08\u96F6\u65B0\u589E commit\uFF09\u3002",
|
|
3500
3606
|
parameters: {
|
|
3501
3607
|
dryRun: { type: "boolean", description: "\u53EA\u8BD5\u8DD1\uFF1A\u4E0D\u5199\u6587\u4EF6\u3001\u4E0D\u63D0\u4EA4\u3001\u4E0D\u5F00 PR" },
|
|
3502
|
-
date: { type: "string", description: "\u6307\u5B9A\u65E5\u671F\uFF08YYYY-MM-DD\uFF09\uFF1B\u7701\u7565\u5219\u7528\u8C03\u5EA6\u65F6\u533A\u7684\u4ECA\u5929" }
|
|
3608
|
+
date: { type: "string", description: "\u6307\u5B9A\u65E5\u671F\uFF08YYYY-MM-DD\uFF09\uFF1B\u7701\u7565\u5219\u7528\u8C03\u5EA6\u65F6\u533A\u7684\u4ECA\u5929" },
|
|
3609
|
+
period: { type: "string", enum: ["daily", "weekly", "monthly"], description: "\u4EA7\u51FA\u5468\u671F\uFF1B\u7701\u7565\u5219\u7528\u8BBE\u7F6E\u91CC\u7684\u9ED8\u8BA4\u5468\u671F" }
|
|
3503
3610
|
},
|
|
3504
3611
|
output: {
|
|
3505
3612
|
schema: RUN_OUTPUT_SCHEMA,
|
|
@@ -3510,6 +3617,7 @@ function createTrendingRunTool(runner) {
|
|
|
3510
3617
|
const summary = await runner.run({
|
|
3511
3618
|
dryRun: args.dryRun === true,
|
|
3512
3619
|
...typeof args.date === "string" && args.date !== "" ? { date: args.date } : {},
|
|
3620
|
+
...isPeriod(args.period) ? { period: args.period } : {},
|
|
3513
3621
|
trigger: "tool"
|
|
3514
3622
|
});
|
|
3515
3623
|
const lines = [];
|
package/lib/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
createTrendingRunTool,
|
|
9
9
|
createUpdateService,
|
|
10
10
|
readPluginInfo
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-QXAKMNHQ.js";
|
|
12
12
|
import "./chunk-XLIFTAVL.js";
|
|
13
13
|
|
|
14
14
|
// src/index.ts
|
|
@@ -53,9 +53,9 @@ function apply(ctx, config = {}) {
|
|
|
53
53
|
store,
|
|
54
54
|
isRunning: () => runner.running !== void 0,
|
|
55
55
|
getTimer: () => get("timer"),
|
|
56
|
-
run: async () => {
|
|
57
|
-
const summary = await runner.run({ trigger: "schedule" });
|
|
58
|
-
log("schedule.run", { ok: summary.ok, skipped: summary.skipped, error: summary.error?.code });
|
|
56
|
+
run: async (_trigger, period) => {
|
|
57
|
+
const summary = await runner.run({ trigger: "schedule", period });
|
|
58
|
+
log("schedule.run", { period, ok: summary.ok, skipped: summary.skipped, error: summary.error?.code });
|
|
59
59
|
},
|
|
60
60
|
onError: (error) => ctx.logger?.warn?.(`[dsh-github-trending] \u5B9A\u65F6\u8FD0\u884C\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`)
|
|
61
61
|
});
|
package/lib/internal.d.ts
CHANGED
|
@@ -164,7 +164,16 @@ type Provider = string;
|
|
|
164
164
|
type CommitMode = 'branch' | 'direct';
|
|
165
165
|
/** 只保留两种真正可用的方式:本机 `gh` 登录态、手动 PAT。历史的 `device` 在归一化时迁移为 `gh`。 */
|
|
166
166
|
type AuthMode = 'pat' | 'gh';
|
|
167
|
-
|
|
167
|
+
/**
|
|
168
|
+
* 榜单周期(core 0.4.0 的 `--period` 三取值)。
|
|
169
|
+
* `trending.since` 复用同一枚举:单次运行「抓取窗口 = 标注周期」由核心保证
|
|
170
|
+
* (`run --period` 会覆盖 `config.trending.since`)。
|
|
171
|
+
*/
|
|
172
|
+
type Period = 'daily' | 'weekly' | 'monthly';
|
|
173
|
+
type Since = Period;
|
|
174
|
+
/** 规范顺序(索引分组、去重、UI 展示都以它为准)。 */
|
|
175
|
+
declare const PERIODS: readonly Period[];
|
|
176
|
+
declare function isPeriod(value: unknown): value is Period;
|
|
168
177
|
interface ModelSettings {
|
|
169
178
|
provider: Provider;
|
|
170
179
|
/** `direct` = 密钥经配置通道直传核心;`proxy` = 宿主 provider 无可导出密钥,走回环代理。 */
|
|
@@ -210,6 +219,11 @@ interface ScheduleSettings {
|
|
|
210
219
|
/** `HH:MM:SS`(本地墙钟,按 timezone 解释)。 */
|
|
211
220
|
time: string;
|
|
212
221
|
timezone: string;
|
|
222
|
+
/**
|
|
223
|
+
* 定时自动产出的周期(插件侧调度概念,核心无此字段)。
|
|
224
|
+
* 每个周期按自己的节奏触发:日=每天、周=周日、月=月末。
|
|
225
|
+
*/
|
|
226
|
+
periods: Period[];
|
|
213
227
|
}
|
|
214
228
|
interface Settings {
|
|
215
229
|
model: ModelSettings;
|
|
@@ -293,9 +307,10 @@ interface CoreConfig {
|
|
|
293
307
|
}
|
|
294
308
|
/**
|
|
295
309
|
* 生成核心配置。`apiKey` 单独传入:它只能来自宿主凭据或用户当次输入,
|
|
296
|
-
* 绝不来自 settings.json
|
|
310
|
+
* 绝不来自 settings.json。`period` 是本次运行的标的周期;核心 `run --period`
|
|
311
|
+
* 会用它覆盖 `trending.since`,这里同步写入以免旧核心/调试时口径不一致。
|
|
297
312
|
*/
|
|
298
|
-
declare function toCoreConfig(settings: Settings, apiKey: string, baseURLOverride?: string): CoreConfig;
|
|
313
|
+
declare function toCoreConfig(settings: Settings, apiKey: string, baseURLOverride?: string, period?: Period): CoreConfig;
|
|
299
314
|
/**
|
|
300
315
|
* 极简 YAML 序列化:只覆盖「对象/数组/字符串/数字/布尔」。
|
|
301
316
|
* 字符串一律用 JSON 双引号形式——JSON 字符串是合法 YAML 标量,且能避免
|
|
@@ -311,8 +326,10 @@ declare const config_DEFAULT_SETTINGS: typeof DEFAULT_SETTINGS;
|
|
|
311
326
|
type config_GithubSettings = GithubSettings;
|
|
312
327
|
type config_ModelSettings = ModelSettings;
|
|
313
328
|
type config_OutputSettings = OutputSettings;
|
|
329
|
+
declare const config_PERIODS: typeof PERIODS;
|
|
314
330
|
declare const config_PROVIDER_BASE_URLS: typeof PROVIDER_BASE_URLS;
|
|
315
331
|
type config_PRSettings = PRSettings;
|
|
332
|
+
type config_Period = Period;
|
|
316
333
|
type config_Problem = Problem;
|
|
317
334
|
type config_Provider = Provider;
|
|
318
335
|
type config_RepoSettings = RepoSettings;
|
|
@@ -323,6 +340,7 @@ type config_SettingsSection = SettingsSection;
|
|
|
323
340
|
type config_Since = Since;
|
|
324
341
|
type config_TrendingSettings = TrendingSettings;
|
|
325
342
|
declare const config_effectiveBaseURL: typeof effectiveBaseURL;
|
|
343
|
+
declare const config_isPeriod: typeof isPeriod;
|
|
326
344
|
declare const config_isValidTimezone: typeof isValidTimezone;
|
|
327
345
|
declare const config_mergeSettingsSection: typeof mergeSettingsSection;
|
|
328
346
|
declare const config_normalizeSettings: typeof normalizeSettings;
|
|
@@ -331,7 +349,7 @@ declare const config_toCoreConfig: typeof toCoreConfig;
|
|
|
331
349
|
declare const config_toYaml: typeof toYaml;
|
|
332
350
|
declare const config_validateSettings: typeof validateSettings;
|
|
333
351
|
declare namespace config {
|
|
334
|
-
export { type config_AuthMode as AuthMode, config_CORE_PROVIDERS as CORE_PROVIDERS, type config_CommitMode as CommitMode, type config_CoreConfig as CoreConfig, config_DEFAULT_SETTINGS as DEFAULT_SETTINGS, type config_GithubSettings as GithubSettings, type config_ModelSettings as ModelSettings, type config_OutputSettings as OutputSettings, config_PROVIDER_BASE_URLS as PROVIDER_BASE_URLS, type config_PRSettings as PRSettings, type config_Problem as Problem, type config_Provider as Provider, type config_RepoSettings as RepoSettings, config_SETTINGS_SECTIONS as SETTINGS_SECTIONS, type config_ScheduleSettings as ScheduleSettings, type config_Settings as Settings, type config_SettingsSection as SettingsSection, type config_Since as Since, type config_TrendingSettings as TrendingSettings, config_effectiveBaseURL as effectiveBaseURL, config_isValidTimezone as isValidTimezone, config_mergeSettingsSection as mergeSettingsSection, config_normalizeSettings as normalizeSettings, config_sectionOfField as sectionOfField, config_toCoreConfig as toCoreConfig, config_toYaml as toYaml, config_validateSettings as validateSettings };
|
|
352
|
+
export { type config_AuthMode as AuthMode, config_CORE_PROVIDERS as CORE_PROVIDERS, type config_CommitMode as CommitMode, type config_CoreConfig as CoreConfig, config_DEFAULT_SETTINGS as DEFAULT_SETTINGS, type config_GithubSettings as GithubSettings, type config_ModelSettings as ModelSettings, type config_OutputSettings as OutputSettings, config_PERIODS as PERIODS, config_PROVIDER_BASE_URLS as PROVIDER_BASE_URLS, type config_PRSettings as PRSettings, type config_Period as Period, type config_Problem as Problem, type config_Provider as Provider, type config_RepoSettings as RepoSettings, config_SETTINGS_SECTIONS as SETTINGS_SECTIONS, type config_ScheduleSettings as ScheduleSettings, type config_Settings as Settings, type config_SettingsSection as SettingsSection, type config_Since as Since, type config_TrendingSettings as TrendingSettings, config_effectiveBaseURL as effectiveBaseURL, config_isPeriod as isPeriod, config_isValidTimezone as isValidTimezone, config_mergeSettingsSection as mergeSettingsSection, config_normalizeSettings as normalizeSettings, config_sectionOfField as sectionOfField, config_toCoreConfig as toCoreConfig, config_toYaml as toYaml, config_validateSettings as validateSettings };
|
|
335
353
|
}
|
|
336
354
|
|
|
337
355
|
declare const PLUGIN_DIR_NAME = "github-trending";
|
|
@@ -397,6 +415,8 @@ interface RunRecord {
|
|
|
397
415
|
files: number;
|
|
398
416
|
/** 运行所属的调度时区日期(YYYY-MM-DD);旧记录可能没有。 */
|
|
399
417
|
date?: string;
|
|
418
|
+
/** 本次产出周期(daily|weekly|monthly);旧记录可能没有。 */
|
|
419
|
+
period?: Period;
|
|
400
420
|
contentHash?: string;
|
|
401
421
|
branch?: string;
|
|
402
422
|
commit?: string;
|
|
@@ -421,8 +441,10 @@ interface PendingPr {
|
|
|
421
441
|
}
|
|
422
442
|
interface PluginState {
|
|
423
443
|
lastRunAt?: string;
|
|
424
|
-
/** 最近一次成功(ok=true,含 skipped
|
|
444
|
+
/** 最近一次成功(ok=true,含 skipped)的运行时间;工具与界面读它。 */
|
|
425
445
|
lastSuccessAt?: string;
|
|
446
|
+
/** 各周期最近一次成功的 ISO 时间;调度按「周期 + 本地日期」判重。 */
|
|
447
|
+
periodSuccess?: Partial<Record<Period, string>>;
|
|
426
448
|
lastDigest?: DigestIdentity;
|
|
427
449
|
lastResult?: RunRecord;
|
|
428
450
|
pendingPr?: PendingPr;
|
|
@@ -729,6 +751,8 @@ interface RunCoreOptions {
|
|
|
729
751
|
/** GitHub token;核心只从 env 读(GITHUB_TOKEN/GH_TOKEN)。 */
|
|
730
752
|
token?: string;
|
|
731
753
|
date?: string;
|
|
754
|
+
/** 本次运行的标的周期(daily|weekly|monthly);核心 `--period` 覆盖抓取 since。 */
|
|
755
|
+
period: Period;
|
|
732
756
|
dryRun: boolean;
|
|
733
757
|
/** 谁触发的运行:核心用它在定时运行时尊重「人工关闭的 PR」。 */
|
|
734
758
|
trigger?: 'manual' | 'schedule';
|
|
@@ -749,6 +773,7 @@ interface RunCoreResult {
|
|
|
749
773
|
declare function buildCoreArgs(channelPath: string, options: {
|
|
750
774
|
dryRun: boolean;
|
|
751
775
|
date?: string;
|
|
776
|
+
period?: Period;
|
|
752
777
|
}): string[];
|
|
753
778
|
declare function runCore(options: RunCoreOptions): Promise<RunCoreResult>;
|
|
754
779
|
|
|
@@ -1187,6 +1212,8 @@ interface RunSummary {
|
|
|
1187
1212
|
skipped: boolean;
|
|
1188
1213
|
dryRun: boolean;
|
|
1189
1214
|
date?: string;
|
|
1215
|
+
/** 本次产出周期(daily|weekly|monthly)。 */
|
|
1216
|
+
period?: Period;
|
|
1190
1217
|
durationMs: number;
|
|
1191
1218
|
coreVersion: string;
|
|
1192
1219
|
files: number;
|
|
@@ -1218,6 +1245,8 @@ interface RunSummary {
|
|
|
1218
1245
|
interface RunRequestOptions {
|
|
1219
1246
|
dryRun?: boolean;
|
|
1220
1247
|
date?: string;
|
|
1248
|
+
/** 标的周期;缺省取 settings.trending.since(手动/工具运行)。 */
|
|
1249
|
+
period?: Period;
|
|
1221
1250
|
trigger: 'manual' | 'schedule' | 'tool';
|
|
1222
1251
|
}
|
|
1223
1252
|
interface RunnerDeps {
|
|
@@ -1229,6 +1258,13 @@ interface RunnerDeps {
|
|
|
1229
1258
|
/** 预览副本这类「可失败但不阻断运行」的事件走这里,不写进 warnings。 */
|
|
1230
1259
|
logger?: (event: string, data?: Record<string, unknown>) => void;
|
|
1231
1260
|
}
|
|
1261
|
+
/**
|
|
1262
|
+
* 与 core `render.NameOf` 对齐的产物基名(不含扩展名):
|
|
1263
|
+
* daily → YYYY-MM-DD;weekly → YYYY-Wnn(ISO 周年/周);monthly → YYYY-MM。
|
|
1264
|
+
* 用于在 outcome.files 里按周期精确挑出本期 digest 的 `.md`(而不是猜)。
|
|
1265
|
+
*/
|
|
1266
|
+
declare function isoWeekName(date: string): string;
|
|
1267
|
+
declare function periodFileBase(period: Period, date: string): string;
|
|
1232
1268
|
declare class Runner {
|
|
1233
1269
|
private active;
|
|
1234
1270
|
private readonly deps;
|
|
@@ -1261,8 +1297,10 @@ type runner_RunSummary = RunSummary;
|
|
|
1261
1297
|
type runner_Runner = Runner;
|
|
1262
1298
|
declare const runner_Runner: typeof Runner;
|
|
1263
1299
|
type runner_RunnerDeps = RunnerDeps;
|
|
1300
|
+
declare const runner_isoWeekName: typeof isoWeekName;
|
|
1301
|
+
declare const runner_periodFileBase: typeof periodFileBase;
|
|
1264
1302
|
declare namespace runner {
|
|
1265
|
-
export { type runner_RunRequestOptions as RunRequestOptions, type runner_RunSummary as RunSummary, runner_Runner as Runner, type runner_RunnerDeps as RunnerDeps };
|
|
1303
|
+
export { type runner_RunRequestOptions as RunRequestOptions, type runner_RunSummary as RunSummary, runner_Runner as Runner, type runner_RunnerDeps as RunnerDeps, runner_isoWeekName as isoWeekName, runner_periodFileBase as periodFileBase };
|
|
1266
1304
|
}
|
|
1267
1305
|
|
|
1268
1306
|
interface PluginInfo {
|
|
@@ -1296,6 +1334,8 @@ interface ScheduleConfig {
|
|
|
1296
1334
|
enabled: boolean;
|
|
1297
1335
|
time: string;
|
|
1298
1336
|
timezone: string;
|
|
1337
|
+
/** 自动产出的周期;每个周期只在「它自己的触发日」跑(日=每天、周=周日、月=月末)。 */
|
|
1338
|
+
periods: Period[];
|
|
1299
1339
|
}
|
|
1300
1340
|
/** 目标时区的墙上时间 → UTC 时刻(按 DST 修正一次,够用且可测)。 */
|
|
1301
1341
|
declare function zonedTimeToUtc(year: number, month: number, day: number, hour: number, minute: number, second: number, timezone: string): Date;
|
|
@@ -1310,9 +1350,14 @@ declare function zonedDateString(date: Date, timezone: string): string;
|
|
|
1310
1350
|
declare function nextOccurrence(now: Date, schedule: ScheduleConfig): Date;
|
|
1311
1351
|
/** 今天这一次是否已过点且在补跑窗口内。 */
|
|
1312
1352
|
declare function isDue(now: Date, schedule: ScheduleConfig): boolean;
|
|
1353
|
+
declare function daysInMonth(year: number, month: number): number;
|
|
1354
|
+
/** 该时刻是否落在某个周期的结算日(按 timezone 的本地日历)。 */
|
|
1355
|
+
declare function isPeriodTriggerDay(period: Period, now: Date, timezone: string): boolean;
|
|
1356
|
+
/** 某周期下一次触发时刻(触发日的墙钟 time,严格晚于 now)。 */
|
|
1357
|
+
declare function nextOccurrenceFor(period: Period, now: Date, schedule: ScheduleConfig): Date;
|
|
1313
1358
|
interface SchedulerDeps {
|
|
1314
1359
|
store: Store;
|
|
1315
|
-
run: (trigger: 'schedule') => Promise<unknown>;
|
|
1360
|
+
run: (trigger: 'schedule', period: Period) => Promise<unknown>;
|
|
1316
1361
|
isRunning: () => boolean;
|
|
1317
1362
|
getTimer: () => {
|
|
1318
1363
|
interval?: (fn: () => void, ms: number) => unknown;
|
|
@@ -1331,13 +1376,16 @@ declare class Scheduler {
|
|
|
1331
1376
|
stop(): void;
|
|
1332
1377
|
/** 公开给测试:判断此刻是否该跑,并按需触发一次。 */
|
|
1333
1378
|
tick(now?: Date): Promise<'skipped' | 'ran' | 'disabled' | 'busy'>;
|
|
1379
|
+
/** 该周期今天是否已经成功过(含无变更跳过)。 */
|
|
1380
|
+
private succeededToday;
|
|
1334
1381
|
private readSchedule;
|
|
1335
|
-
/**
|
|
1382
|
+
/** 下一次运行(取所有已启用周期里最近的一次;禁用时为 undefined)。 */
|
|
1336
1383
|
next(): Date | undefined;
|
|
1337
1384
|
describe(state: PluginState, now?: Date): {
|
|
1338
1385
|
enabled: boolean;
|
|
1339
1386
|
time: string;
|
|
1340
1387
|
timezone: string;
|
|
1388
|
+
periods: Period[];
|
|
1341
1389
|
nextRunAt?: string;
|
|
1342
1390
|
lastRunDate?: string;
|
|
1343
1391
|
catchUpMinutes: number;
|
|
@@ -1349,13 +1397,16 @@ type schedule_ScheduleConfig = ScheduleConfig;
|
|
|
1349
1397
|
type schedule_Scheduler = Scheduler;
|
|
1350
1398
|
declare const schedule_Scheduler: typeof Scheduler;
|
|
1351
1399
|
type schedule_SchedulerDeps = SchedulerDeps;
|
|
1400
|
+
declare const schedule_daysInMonth: typeof daysInMonth;
|
|
1352
1401
|
declare const schedule_isDue: typeof isDue;
|
|
1402
|
+
declare const schedule_isPeriodTriggerDay: typeof isPeriodTriggerDay;
|
|
1353
1403
|
declare const schedule_nextOccurrence: typeof nextOccurrence;
|
|
1404
|
+
declare const schedule_nextOccurrenceFor: typeof nextOccurrenceFor;
|
|
1354
1405
|
declare const schedule_parseClock: typeof parseClock;
|
|
1355
1406
|
declare const schedule_zonedDateString: typeof zonedDateString;
|
|
1356
1407
|
declare const schedule_zonedTimeToUtc: typeof zonedTimeToUtc;
|
|
1357
1408
|
declare namespace schedule {
|
|
1358
|
-
export { type schedule_ScheduleConfig as ScheduleConfig, schedule_Scheduler as Scheduler, type schedule_SchedulerDeps as SchedulerDeps, schedule_isDue as isDue, schedule_nextOccurrence as nextOccurrence, schedule_parseClock as parseClock, schedule_zonedDateString as zonedDateString, schedule_zonedTimeToUtc as zonedTimeToUtc };
|
|
1409
|
+
export { type schedule_ScheduleConfig as ScheduleConfig, schedule_Scheduler as Scheduler, type schedule_SchedulerDeps as SchedulerDeps, schedule_daysInMonth as daysInMonth, schedule_isDue as isDue, schedule_isPeriodTriggerDay as isPeriodTriggerDay, schedule_nextOccurrence as nextOccurrence, schedule_nextOccurrenceFor as nextOccurrenceFor, schedule_parseClock as parseClock, schedule_zonedDateString as zonedDateString, schedule_zonedTimeToUtc as zonedTimeToUtc };
|
|
1359
1410
|
}
|
|
1360
1411
|
|
|
1361
1412
|
/**
|
|
@@ -1447,13 +1498,6 @@ declare namespace importCli {
|
|
|
1447
1498
|
export { type importCli_ImportCandidate as ImportCandidate, type importCli_ImportResult as ImportResult, importCli_cliConfigCandidates as cliConfigCandidates, importCli_importFirstAvailable as importFirstAvailable, importCli_importFromCli as importFromCli, importCli_parseSimpleYaml as parseSimpleYaml };
|
|
1448
1499
|
}
|
|
1449
1500
|
|
|
1450
|
-
/**
|
|
1451
|
-
* 两个对外工具:`trending_run` / `trending_history`(规格 §8)。
|
|
1452
|
-
*
|
|
1453
|
-
* 工具名是**稳定对外接口**:一旦发布不再改名;需要新能力时新增工具。
|
|
1454
|
-
* 参数方言用宿主 `dsh-tools` 的「内联 required: true」,输出用标准 JSON Schema。
|
|
1455
|
-
*/
|
|
1456
|
-
|
|
1457
1501
|
declare function createTrendingRunTool(runner: Runner): {
|
|
1458
1502
|
name: string;
|
|
1459
1503
|
description: string;
|
|
@@ -1466,6 +1510,11 @@ declare function createTrendingRunTool(runner: Runner): {
|
|
|
1466
1510
|
type: string;
|
|
1467
1511
|
description: string;
|
|
1468
1512
|
};
|
|
1513
|
+
period: {
|
|
1514
|
+
type: string;
|
|
1515
|
+
enum: string[];
|
|
1516
|
+
description: string;
|
|
1517
|
+
};
|
|
1469
1518
|
};
|
|
1470
1519
|
output: {
|
|
1471
1520
|
schema: {
|
|
@@ -1533,6 +1582,7 @@ declare function createTrendingRunTool(runner: Runner): {
|
|
|
1533
1582
|
execute(args?: {
|
|
1534
1583
|
dryRun?: boolean;
|
|
1535
1584
|
date?: string;
|
|
1585
|
+
period?: string;
|
|
1536
1586
|
}, exec?: {
|
|
1537
1587
|
signal?: AbortSignal;
|
|
1538
1588
|
}): Promise<{
|
package/lib/internal.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morehao/dsh-github-trending",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "GitHub 趋势日报插件:每天定时 spawn 本地 trendradar 核心,抓取 Trending、生成中文简介与总结,提交到你的仓库并自动合并 PR。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
},
|
|
60
60
|
"core": {
|
|
61
61
|
"repo": "morehao/trendradar",
|
|
62
|
-
"version": "0.
|
|
62
|
+
"version": "0.4.0"
|
|
63
63
|
},
|
|
64
64
|
"client": {
|
|
65
65
|
"platform": "web",
|