@morehao/dsh-github-trending 0.2.0 → 0.3.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 +34 -0
- package/README.md +24 -11
- package/client.js +1987 -186
- package/lib/{chunk-HIJIQJCZ.js → chunk-X4PBXIZE.js} +376 -80
- package/lib/index.js +29 -11
- package/lib/internal.d.ts +137 -86
- package/lib/internal.js +3 -1
- package/package.json +7 -6
- package/contract/CONTRACT.sha256 +0 -10
- package/contract/UPSTREAM.json +0 -37
- package/contract/fixtures/digest-degraded.json +0 -39
- package/contract/fixtures/digest-empty-lang.json +0 -39
- package/contract/fixtures/digest-normal.json +0 -91
- package/contract/schema/api.v1.yaml +0 -70
- package/contract/schema/digest.v1.json +0 -24
- package/contract/schema/ipc.v1.json +0 -104
- package/contract/schema/model.v1.json +0 -46
package/lib/index.js
CHANGED
|
@@ -5,22 +5,16 @@ import {
|
|
|
5
5
|
Store,
|
|
6
6
|
createRoutes,
|
|
7
7
|
createTrendingHistoryTool,
|
|
8
|
-
createTrendingRunTool
|
|
9
|
-
|
|
8
|
+
createTrendingRunTool,
|
|
9
|
+
readPluginInfo
|
|
10
|
+
} from "./chunk-X4PBXIZE.js";
|
|
10
11
|
import "./chunk-XLIFTAVL.js";
|
|
11
12
|
|
|
12
13
|
// src/index.ts
|
|
13
|
-
import { createRequire } from "module";
|
|
14
14
|
var name = "dsh-github-trending";
|
|
15
15
|
var inject = ["webServer"];
|
|
16
16
|
function readVersion() {
|
|
17
|
-
|
|
18
|
-
const require2 = createRequire(import.meta.url);
|
|
19
|
-
const pkg = require2("../package.json");
|
|
20
|
-
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
21
|
-
} catch {
|
|
22
|
-
return "0.0.0";
|
|
23
|
-
}
|
|
17
|
+
return readPluginInfo().version;
|
|
24
18
|
}
|
|
25
19
|
var VERSION = readVersion();
|
|
26
20
|
function apply(ctx, config = {}) {
|
|
@@ -31,6 +25,18 @@ function apply(ctx, config = {}) {
|
|
|
31
25
|
ctx.logger?.info?.(`[dsh-github-trending] ${event}${suffix}`);
|
|
32
26
|
};
|
|
33
27
|
const store = new Store({ env, ...config.stateDir === void 0 ? {} : { dir: config.stateDir } });
|
|
28
|
+
try {
|
|
29
|
+
const migration = store.migrateLegacyState();
|
|
30
|
+
if (migration.stateChanged || migration.historyMarked > 0) {
|
|
31
|
+
log("state.migrated", {
|
|
32
|
+
clearedDigest: migration.clearedDigest,
|
|
33
|
+
clearedPendingPr: migration.clearedPendingPr,
|
|
34
|
+
historyMarked: migration.historyMarked
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
ctx.logger?.warn?.(`[dsh-github-trending] \u65E7\u72B6\u6001\u8FC1\u79FB\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`);
|
|
39
|
+
}
|
|
34
40
|
if (config.corePath !== void 0 && config.corePath !== "") {
|
|
35
41
|
const settings = store.readSettings();
|
|
36
42
|
if (settings.corePath === "") store.writeSettings({ ...settings, corePath: config.corePath });
|
|
@@ -39,6 +45,7 @@ function apply(ctx, config = {}) {
|
|
|
39
45
|
store,
|
|
40
46
|
get,
|
|
41
47
|
env,
|
|
48
|
+
logger: log,
|
|
42
49
|
onFinished: (summary) => log("run.finished", { id: summary.id, ok: summary.ok, skipped: summary.skipped, files: summary.files })
|
|
43
50
|
});
|
|
44
51
|
const scheduler = new Scheduler({
|
|
@@ -51,7 +58,18 @@ function apply(ctx, config = {}) {
|
|
|
51
58
|
},
|
|
52
59
|
onError: (error) => ctx.logger?.warn?.(`[dsh-github-trending] \u5B9A\u65F6\u8FD0\u884C\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`)
|
|
53
60
|
});
|
|
54
|
-
const
|
|
61
|
+
const pluginInfo = readPluginInfo();
|
|
62
|
+
const handler = createRoutes({
|
|
63
|
+
store,
|
|
64
|
+
runner,
|
|
65
|
+
scheduler,
|
|
66
|
+
get,
|
|
67
|
+
env,
|
|
68
|
+
version: VERSION,
|
|
69
|
+
pluginName: pluginInfo.name,
|
|
70
|
+
...pluginInfo.repository === void 0 ? {} : { repository: pluginInfo.repository },
|
|
71
|
+
logger: log
|
|
72
|
+
});
|
|
55
73
|
const register = () => {
|
|
56
74
|
const webServer = ctx.webServer ?? get("webServer");
|
|
57
75
|
if (webServer === void 0 || typeof webServer.register !== "function") {
|
package/lib/internal.d.ts
CHANGED
|
@@ -1,61 +1,8 @@
|
|
|
1
|
+
import { ErrorCode, FileArtifact, ErrorInfo, RunRequest, RunResult, State, StateRun } from '@morehao/trendradar-contract';
|
|
1
2
|
import http from 'node:http';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
* 上游:https://github.com/morehao/trendradar.git@5cf0ad26c5748cf2bc9a1cc4ce66a09411d590f7
|
|
6
|
-
* aggregate:4717074d26d1a6d85c1885bd052d411f81c20b39467cb81c398f565bbe34dcde
|
|
7
|
-
* 重新生成:npm run gen:ipc
|
|
8
|
-
*/
|
|
9
|
-
type ErrorCode = "INTERNAL" | "CONFIG_INVALID" | "PAYLOAD_TOO_LARGE" | "FETCH_FAILED" | "FETCH_RATE_LIMITED" | "PARSE_FAILED" | "MODEL_UNAUTHORIZED" | "MODEL_RATE_LIMITED" | "MODEL_TIMEOUT" | "MODEL_FAILED" | "MODEL_BAD_OUTPUT" | "GIT_NOT_INSTALLED" | "REPO_NOT_GIT" | "REPO_DIRTY" | "GIT_OPERATION_FAILED" | "PR_FAILED" | "NOT_MERGEABLE";
|
|
10
|
-
interface ErrorInfo {
|
|
11
|
-
code: ErrorCode;
|
|
12
|
-
message: string;
|
|
13
|
-
retriable: boolean;
|
|
14
|
-
details?: Record<string, unknown>;
|
|
15
|
-
[key: string]: unknown;
|
|
16
|
-
}
|
|
17
|
-
interface FileArtifact {
|
|
18
|
-
path: string;
|
|
19
|
-
bytes: number;
|
|
20
|
-
sha256: string;
|
|
21
|
-
[key: string]: unknown;
|
|
22
|
-
}
|
|
23
|
-
interface RunRequest {
|
|
24
|
-
ipcSchema: 1;
|
|
25
|
-
command: "run" | "serve" | "config" | "version";
|
|
26
|
-
configPath?: string;
|
|
27
|
-
date?: string;
|
|
28
|
-
dryRun?: boolean;
|
|
29
|
-
outputDir?: string;
|
|
30
|
-
}
|
|
31
|
-
interface RunResult {
|
|
32
|
-
ipcSchema: 1;
|
|
33
|
-
ok: boolean;
|
|
34
|
-
command: "run" | "serve" | "config" | "version";
|
|
35
|
-
startedAt: string;
|
|
36
|
-
finishedAt: string;
|
|
37
|
-
skipped: boolean;
|
|
38
|
-
files: FileArtifact[];
|
|
39
|
-
error?: ErrorInfo;
|
|
40
|
-
warnings?: string[];
|
|
41
|
-
[key: string]: unknown;
|
|
42
|
-
}
|
|
43
|
-
interface StateRun {
|
|
44
|
-
date: string;
|
|
45
|
-
contentHash: string;
|
|
46
|
-
outputDir?: string;
|
|
47
|
-
runAt: string;
|
|
48
|
-
[key: string]: unknown;
|
|
49
|
-
}
|
|
50
|
-
interface State {
|
|
51
|
-
ipcSchema: 1;
|
|
52
|
-
lastRun?: StateRun;
|
|
53
|
-
[key: string]: unknown;
|
|
54
|
-
}
|
|
55
|
-
declare const IPC_SCHEMA_VERSION: 1;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* 错误码:核心码(`ipc.v1` 的 17 个)+ 插件自己的码。
|
|
5
|
+
* 错误码:核心码(契约包 `ipc.v2` / `errors.v2` 的 17 个)+ 插件自己的码。
|
|
59
6
|
*
|
|
60
7
|
* 核心当前只在 stderr 打 `error: <CODE>: <message>`,模型/抓取的失败甚至没有码,
|
|
61
8
|
* 所以这里承担两件事:
|
|
@@ -79,8 +26,15 @@ declare function isRetriable(code: string): boolean;
|
|
|
79
26
|
/** 错误码 → 界面落点。UI 用这个决定红字出现在哪个区块。 */
|
|
80
27
|
type ErrorArea = 'core' | 'model' | 'repo' | 'auth' | 'run';
|
|
81
28
|
declare function areaOf(code: string): ErrorArea;
|
|
29
|
+
/**
|
|
30
|
+
* push 失败的细分原因。核心新版本会在 `details.reason` 里给出;旧核心只有
|
|
31
|
+
* stderr 时按文本兜底。`stale-lease` 必须排在 `non-fast-forward` 之前:
|
|
32
|
+
* 前者是 lease 基线过期,后者是历史分叉,下一步完全不同。
|
|
33
|
+
*/
|
|
34
|
+
type GitReason = 'stale-lease' | 'non-fast-forward' | 'auth' | 'protected' | 'hook' | 'pathspec' | 'unknown';
|
|
35
|
+
declare function reasonOf(code: string, message?: string, details?: Record<string, unknown>): string | undefined;
|
|
82
36
|
/** 每种码一句「下一步怎么做」,避免界面只给码不给路。 */
|
|
83
|
-
declare function hintOf(code: string, message?: string): string;
|
|
37
|
+
declare function hintOf(code: string, message?: string, reason?: string): string;
|
|
84
38
|
/**
|
|
85
39
|
* 从核心的 stderr 里解出一个结构化错误。
|
|
86
40
|
*
|
|
@@ -99,38 +53,32 @@ declare function redact(input: string, secrets?: Array<string | undefined>): str
|
|
|
99
53
|
type errors_AnyCode = AnyCode;
|
|
100
54
|
type errors_ClassifiedError = ClassifiedError;
|
|
101
55
|
type errors_ErrorArea = ErrorArea;
|
|
56
|
+
type errors_GitReason = GitReason;
|
|
102
57
|
declare const errors_PLUGIN_CODES: typeof PLUGIN_CODES;
|
|
103
58
|
type errors_PluginCode = PluginCode;
|
|
104
59
|
declare const errors_areaOf: typeof areaOf;
|
|
105
60
|
declare const errors_classifyStderr: typeof classifyStderr;
|
|
106
61
|
declare const errors_hintOf: typeof hintOf;
|
|
107
62
|
declare const errors_isRetriable: typeof isRetriable;
|
|
63
|
+
declare const errors_reasonOf: typeof reasonOf;
|
|
108
64
|
declare const errors_redact: typeof redact;
|
|
109
65
|
declare namespace errors {
|
|
110
|
-
export { type errors_AnyCode as AnyCode, type errors_ClassifiedError as ClassifiedError, type errors_ErrorArea as ErrorArea, errors_PLUGIN_CODES as PLUGIN_CODES, type errors_PluginCode as PluginCode, errors_areaOf as areaOf, errors_classifyStderr as classifyStderr, errors_hintOf as hintOf, errors_isRetriable as isRetriable, errors_redact as redact };
|
|
66
|
+
export { type errors_AnyCode as AnyCode, type errors_ClassifiedError as ClassifiedError, type errors_ErrorArea as ErrorArea, type errors_GitReason as GitReason, errors_PLUGIN_CODES as PLUGIN_CODES, type errors_PluginCode as PluginCode, errors_areaOf as areaOf, errors_classifyStderr as classifyStderr, errors_hintOf as hintOf, errors_isRetriable as isRetriable, errors_reasonOf as reasonOf, errors_redact as redact };
|
|
111
67
|
}
|
|
112
68
|
|
|
113
|
-
/**
|
|
114
|
-
|
|
115
|
-
*
|
|
116
|
-
* 类型来自 `contract/schema/ipc.v1.json` 的生成物(`ipc.gen.ts`);本文件只放
|
|
117
|
-
* 契约没有、但插件必须理解的东西:核心 `version --json` 的握手形状、核心 `run --json`
|
|
118
|
-
* 的输出形状,以及把两种结果形状归一化成 `RunOutcome` 的解析器。
|
|
119
|
-
*
|
|
120
|
-
* 为什么同时接受两种形状:`contract/schema/ipc.v1.json` 定义的是 `RunResult`
|
|
121
|
-
* (`ok`/`startedAt`/`error.code`),而当前核心的 `run --json` 输出的是一份更小的
|
|
122
|
-
* `runOutput`(`files`/`skipped`/`branch`/`prUrl`/`digest.contentHash`),错误只走
|
|
123
|
-
* stderr 文本。核心补上信封后,`parseRunResult` 无需改动即可接住。
|
|
124
|
-
*/
|
|
125
|
-
|
|
69
|
+
/** 插件实现所对应的 IPC 契约版本(契约包 `ipc.v2.json` 的 wire 整数)。 */
|
|
70
|
+
declare const IPC_SCHEMA_VERSION: 2;
|
|
126
71
|
/** 插件能接受的 digest 契约版本(与核心 `version --json` 的 digestSchema 比对)。 */
|
|
127
|
-
declare const SUPPORTED_DIGEST_SCHEMA
|
|
72
|
+
declare const SUPPORTED_DIGEST_SCHEMA: 2;
|
|
73
|
+
/** 契约包 `contractVersion` 的主版本;0.x 期间同主版本即视为同一契约线。 */
|
|
74
|
+
declare const SUPPORTED_CONTRACT_MAJOR = 0;
|
|
128
75
|
/** 核心 `version --json` 的输出。 */
|
|
129
76
|
interface VersionInfo {
|
|
130
77
|
version: string;
|
|
131
78
|
commit?: string;
|
|
132
79
|
ipcSchema: number;
|
|
133
80
|
digestSchema: number;
|
|
81
|
+
contractVersion?: string;
|
|
134
82
|
supportedProviders?: string[];
|
|
135
83
|
}
|
|
136
84
|
/** 插件侧的错误信息:`code` 可能是核心码,也可能是插件码(`CORE_*` 等)。 */
|
|
@@ -150,6 +98,8 @@ interface RunOutcome {
|
|
|
150
98
|
branch?: string;
|
|
151
99
|
commit?: string;
|
|
152
100
|
prUrl?: string;
|
|
101
|
+
/** 核心给出的跳过原因:merged | pr-closed | unchanged(可选,旧核心没有)。 */
|
|
102
|
+
skipReason?: string;
|
|
153
103
|
error?: RunError;
|
|
154
104
|
warnings: string[];
|
|
155
105
|
startedAt: string;
|
|
@@ -168,6 +118,7 @@ declare class IpcError extends Error {
|
|
|
168
118
|
declare function parseJsonObject(text: string): Record<string, unknown>;
|
|
169
119
|
declare function isVersionInfo(value: unknown): value is VersionInfo;
|
|
170
120
|
declare function parseVersionInfo(text: string): VersionInfo;
|
|
121
|
+
/** 契约包 `RunResult` → 插件内部 `RunOutcome` 的唯一映射。 */
|
|
171
122
|
interface ParseRunOptions {
|
|
172
123
|
coreVersion: string;
|
|
173
124
|
startedAt: string;
|
|
@@ -178,33 +129,34 @@ interface ParseRunOptions {
|
|
|
178
129
|
/**
|
|
179
130
|
* 把核心 `run --json` 的 stdout 归一化成 `RunOutcome`。
|
|
180
131
|
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
132
|
+
* 唯一接受的形状是契约 `RunResult`(`ipcSchema=2` / `command=run`),并用契约包
|
|
133
|
+
* 自带的 ajv 校验器校验。任何其它形状直接 `IPC_INVALID`——结果信封读错会把失败
|
|
134
|
+
* 当成功写进状态。
|
|
184
135
|
*/
|
|
185
136
|
declare function parseRunResult(text: string, options: ParseRunOptions): RunOutcome;
|
|
186
137
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
138
|
+
declare const ipc_ErrorCode: typeof ErrorCode;
|
|
139
|
+
declare const ipc_ErrorInfo: typeof ErrorInfo;
|
|
140
|
+
declare const ipc_FileArtifact: typeof FileArtifact;
|
|
190
141
|
declare const ipc_IPC_SCHEMA_VERSION: typeof IPC_SCHEMA_VERSION;
|
|
191
142
|
type ipc_IpcError = IpcError;
|
|
192
143
|
declare const ipc_IpcError: typeof IpcError;
|
|
193
144
|
type ipc_ParseRunOptions = ParseRunOptions;
|
|
194
145
|
type ipc_RunError = RunError;
|
|
195
146
|
type ipc_RunOutcome = RunOutcome;
|
|
196
|
-
|
|
197
|
-
|
|
147
|
+
declare const ipc_RunRequest: typeof RunRequest;
|
|
148
|
+
declare const ipc_RunResult: typeof RunResult;
|
|
149
|
+
declare const ipc_SUPPORTED_CONTRACT_MAJOR: typeof SUPPORTED_CONTRACT_MAJOR;
|
|
198
150
|
declare const ipc_SUPPORTED_DIGEST_SCHEMA: typeof SUPPORTED_DIGEST_SCHEMA;
|
|
199
|
-
|
|
200
|
-
|
|
151
|
+
declare const ipc_State: typeof State;
|
|
152
|
+
declare const ipc_StateRun: typeof StateRun;
|
|
201
153
|
type ipc_VersionInfo = VersionInfo;
|
|
202
154
|
declare const ipc_isVersionInfo: typeof isVersionInfo;
|
|
203
155
|
declare const ipc_parseJsonObject: typeof parseJsonObject;
|
|
204
156
|
declare const ipc_parseRunResult: typeof parseRunResult;
|
|
205
157
|
declare const ipc_parseVersionInfo: typeof parseVersionInfo;
|
|
206
158
|
declare namespace ipc {
|
|
207
|
-
export {
|
|
159
|
+
export { ipc_ErrorCode as ErrorCode, ipc_ErrorInfo as ErrorInfo, ipc_FileArtifact as FileArtifact, ipc_IPC_SCHEMA_VERSION as IPC_SCHEMA_VERSION, ipc_IpcError as IpcError, type ipc_ParseRunOptions as ParseRunOptions, type ipc_RunError as RunError, type ipc_RunOutcome as RunOutcome, ipc_RunRequest as RunRequest, ipc_RunResult as RunResult, ipc_SUPPORTED_CONTRACT_MAJOR as SUPPORTED_CONTRACT_MAJOR, ipc_SUPPORTED_DIGEST_SCHEMA as SUPPORTED_DIGEST_SCHEMA, ipc_State as State, ipc_StateRun as StateRun, type ipc_VersionInfo as VersionInfo, ipc_isVersionInfo as isVersionInfo, ipc_parseJsonObject as parseJsonObject, ipc_parseRunResult as parseRunResult, ipc_parseVersionInfo as parseVersionInfo };
|
|
208
160
|
}
|
|
209
161
|
|
|
210
162
|
type Provider = string;
|
|
@@ -403,11 +355,30 @@ declare namespace paths {
|
|
|
403
355
|
}
|
|
404
356
|
|
|
405
357
|
declare const HISTORY_LIMIT = 30;
|
|
358
|
+
/** 预览副本上限:最多保留 30 份,和 history 的 30 天窗口对齐。 */
|
|
359
|
+
declare const PREVIEW_LIMIT = 30;
|
|
360
|
+
/** 一次预览只读这么多字节;超过的不复制,避免把大文件塞进状态目录。 */
|
|
361
|
+
declare const PREVIEW_MAX_BYTES: number;
|
|
362
|
+
/** 契约 v2 的固定内容哈希算法;旧版状态没有这个字段,据此识别 legacy。 */
|
|
363
|
+
declare const CONTENT_HASH_ALGO_V2 = "jcs-sha256-v1";
|
|
406
364
|
interface DigestIdentity {
|
|
407
365
|
date: string;
|
|
408
366
|
contentHash: string;
|
|
367
|
+
/** 产出该 hash 的算法;v2 新写的一律是 jcs-sha256-v1。 */
|
|
368
|
+
contentHashAlgo: string;
|
|
409
369
|
coreVersion: string;
|
|
410
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* 预览副本的元数据:只记「哪份文件、多大、内容 sha256」,正文进 previews/。
|
|
373
|
+
* `file` 是仓库内相对路径,不落绝对路径(避免把用户目录写进状态)。
|
|
374
|
+
*/
|
|
375
|
+
interface PreviewMeta {
|
|
376
|
+
file: string;
|
|
377
|
+
bytes: number;
|
|
378
|
+
sha256: string;
|
|
379
|
+
/** 只读接口截断时为 true;当前 512 KB 上限下正常不会出现。 */
|
|
380
|
+
truncated?: boolean;
|
|
381
|
+
}
|
|
411
382
|
interface RunRecord {
|
|
412
383
|
id: string;
|
|
413
384
|
at: string;
|
|
@@ -417,12 +388,19 @@ interface RunRecord {
|
|
|
417
388
|
/** `null` 表示没有错误。 */
|
|
418
389
|
errorCode?: string;
|
|
419
390
|
errorMessage?: string;
|
|
391
|
+
/** push 等失败的机器可判定原因(stale-lease / non-fast-forward / auth ...)。 */
|
|
392
|
+
errorReason?: string;
|
|
393
|
+
/** 跳过原因:merged / pr-closed / unchanged。 */
|
|
394
|
+
skipReason?: string;
|
|
420
395
|
durationMs: number;
|
|
421
396
|
files: number;
|
|
397
|
+
/** 运行所属的调度时区日期(YYYY-MM-DD);旧记录可能没有。 */
|
|
398
|
+
date?: string;
|
|
422
399
|
contentHash?: string;
|
|
423
400
|
branch?: string;
|
|
424
401
|
commit?: string;
|
|
425
402
|
prUrl?: string;
|
|
403
|
+
preview?: PreviewMeta;
|
|
426
404
|
coreVersion: string;
|
|
427
405
|
model: {
|
|
428
406
|
provider: string;
|
|
@@ -431,6 +409,8 @@ interface RunRecord {
|
|
|
431
409
|
languages: string[];
|
|
432
410
|
warnings: string[];
|
|
433
411
|
logFile?: string;
|
|
412
|
+
/** true = 契约 v1 时代留下的旧记录(contentHash 算法不同,不可与 v2 比较)。 */
|
|
413
|
+
legacy?: boolean;
|
|
434
414
|
}
|
|
435
415
|
interface PendingPr {
|
|
436
416
|
url: string;
|
|
@@ -440,6 +420,8 @@ interface PendingPr {
|
|
|
440
420
|
}
|
|
441
421
|
interface PluginState {
|
|
442
422
|
lastRunAt?: string;
|
|
423
|
+
/** 最近一次成功(ok=true,含 skipped)的运行时间;调度用它锁当天。 */
|
|
424
|
+
lastSuccessAt?: string;
|
|
443
425
|
lastDigest?: DigestIdentity;
|
|
444
426
|
lastResult?: RunRecord;
|
|
445
427
|
pendingPr?: PendingPr;
|
|
@@ -447,6 +429,12 @@ interface PluginState {
|
|
|
447
429
|
interface HistoryFile {
|
|
448
430
|
records: RunRecord[];
|
|
449
431
|
}
|
|
432
|
+
interface LegacyMigrationResult {
|
|
433
|
+
stateChanged: boolean;
|
|
434
|
+
clearedDigest: boolean;
|
|
435
|
+
clearedPendingPr: boolean;
|
|
436
|
+
historyMarked: number;
|
|
437
|
+
}
|
|
450
438
|
/** 原子写:同目录临时文件写完再 rename,权限 0600。 */
|
|
451
439
|
declare function writeJsonAtomic(file: string, value: unknown): void;
|
|
452
440
|
interface StoreOptions {
|
|
@@ -462,26 +450,50 @@ declare class Store {
|
|
|
462
450
|
get statePath(): string;
|
|
463
451
|
get historyPath(): string;
|
|
464
452
|
get logsPath(): string;
|
|
453
|
+
get previewsPath(): string;
|
|
465
454
|
readSettings(): Settings;
|
|
466
455
|
writeSettings(settings: Settings): void;
|
|
467
456
|
readState(): PluginState;
|
|
468
457
|
writeState(state: PluginState): void;
|
|
458
|
+
/**
|
|
459
|
+
* 把 v1 时代遗留的 state / history 一次性清洗落盘:清掉不可信的 lastDigest 与
|
|
460
|
+
* 与之绑定的 pendingPr,并给旧历史记录打 legacy 标。插件启动时调用一次。
|
|
461
|
+
*/
|
|
462
|
+
migrateLegacyState(): LegacyMigrationResult;
|
|
469
463
|
readHistory(): RunRecord[];
|
|
464
|
+
/** 原始历史(不做 legacy 标注):写入路径用它,避免把「读时标注」持久化。 */
|
|
465
|
+
private readHistoryRaw;
|
|
470
466
|
/** 记录一次运行:历史裁剪到 30 条,state 同步更新。 */
|
|
471
467
|
recordRun(record: RunRecord, next?: Partial<PluginState>): void;
|
|
472
468
|
newLogPath(id: string): string;
|
|
473
469
|
appendLog(file: string, line: string): void;
|
|
474
470
|
hasLog(file: string): boolean;
|
|
475
471
|
readLog(file: string, maxBytes?: number): string;
|
|
472
|
+
/** 预览副本路径;写入前才建目录,读接口不会凭空创建目录。 */
|
|
473
|
+
previewPath(id: string): string;
|
|
474
|
+
/** 写预览副本:同目录临时文件 + rename、0600;写完裁到 30 份。 */
|
|
475
|
+
writePreview(id: string, text: string): void;
|
|
476
|
+
/** 读预览副本;不存在/不可读返回 undefined,超过上限时截断。 */
|
|
477
|
+
readPreview(id: string, maxBytes?: number): {
|
|
478
|
+
text: string;
|
|
479
|
+
truncated: boolean;
|
|
480
|
+
} | undefined;
|
|
481
|
+
/** 只保留最近 `keep` 份预览副本;按 mtime 倒序,任何错误都忽略。 */
|
|
482
|
+
prunePreviews(keep?: number): void;
|
|
476
483
|
}
|
|
477
484
|
/** 运行 id:时间前缀便于按时间排序,随机后缀避免同毫秒撞车。 */
|
|
478
485
|
declare function newRunId(now?: Date): string;
|
|
479
486
|
|
|
487
|
+
declare const state_CONTENT_HASH_ALGO_V2: typeof CONTENT_HASH_ALGO_V2;
|
|
480
488
|
type state_DigestIdentity = DigestIdentity;
|
|
481
489
|
declare const state_HISTORY_LIMIT: typeof HISTORY_LIMIT;
|
|
482
490
|
type state_HistoryFile = HistoryFile;
|
|
491
|
+
type state_LegacyMigrationResult = LegacyMigrationResult;
|
|
492
|
+
declare const state_PREVIEW_LIMIT: typeof PREVIEW_LIMIT;
|
|
493
|
+
declare const state_PREVIEW_MAX_BYTES: typeof PREVIEW_MAX_BYTES;
|
|
483
494
|
type state_PendingPr = PendingPr;
|
|
484
495
|
type state_PluginState = PluginState;
|
|
496
|
+
type state_PreviewMeta = PreviewMeta;
|
|
485
497
|
type state_RunRecord = RunRecord;
|
|
486
498
|
type state_Store = Store;
|
|
487
499
|
declare const state_Store: typeof Store;
|
|
@@ -489,7 +501,7 @@ type state_StoreOptions = StoreOptions;
|
|
|
489
501
|
declare const state_newRunId: typeof newRunId;
|
|
490
502
|
declare const state_writeJsonAtomic: typeof writeJsonAtomic;
|
|
491
503
|
declare namespace state {
|
|
492
|
-
export { type state_DigestIdentity as DigestIdentity, state_HISTORY_LIMIT as HISTORY_LIMIT, type state_HistoryFile as HistoryFile, type state_PendingPr as PendingPr, type state_PluginState as PluginState, type state_RunRecord as RunRecord, state_Store as Store, type state_StoreOptions as StoreOptions, state_newRunId as newRunId, state_writeJsonAtomic as writeJsonAtomic };
|
|
504
|
+
export { state_CONTENT_HASH_ALGO_V2 as CONTENT_HASH_ALGO_V2, type state_DigestIdentity as DigestIdentity, state_HISTORY_LIMIT as HISTORY_LIMIT, type state_HistoryFile as HistoryFile, type state_LegacyMigrationResult as LegacyMigrationResult, state_PREVIEW_LIMIT as PREVIEW_LIMIT, state_PREVIEW_MAX_BYTES as PREVIEW_MAX_BYTES, type state_PendingPr as PendingPr, type state_PluginState as PluginState, type state_PreviewMeta as PreviewMeta, type state_RunRecord as RunRecord, state_Store as Store, type state_StoreOptions as StoreOptions, state_newRunId as newRunId, state_writeJsonAtomic as writeJsonAtomic };
|
|
493
505
|
}
|
|
494
506
|
|
|
495
507
|
interface CoreRelease {
|
|
@@ -509,7 +521,7 @@ interface ReleaseDescriptor extends CoreRelease {
|
|
|
509
521
|
binaryPath: string;
|
|
510
522
|
cached: boolean;
|
|
511
523
|
}
|
|
512
|
-
/** 从本包的 package.json 读核心版本(`
|
|
524
|
+
/** 从本包的 package.json 读核心版本(`scripts/set-core-version.mjs` 会更新它)。 */
|
|
513
525
|
declare function readCoreRelease(pkg?: {
|
|
514
526
|
dsh?: {
|
|
515
527
|
core?: {
|
|
@@ -717,6 +729,8 @@ interface RunCoreOptions {
|
|
|
717
729
|
token?: string;
|
|
718
730
|
date?: string;
|
|
719
731
|
dryRun: boolean;
|
|
732
|
+
/** 谁触发的运行:核心用它在定时运行时尊重「人工关闭的 PR」。 */
|
|
733
|
+
trigger?: 'manual' | 'schedule';
|
|
720
734
|
/** 回环代理地址:覆盖配置里的 model.baseURL(代理模式专用)。 */
|
|
721
735
|
baseURLOverride?: string;
|
|
722
736
|
signal?: AbortSignal;
|
|
@@ -1177,7 +1191,11 @@ interface RunSummary {
|
|
|
1177
1191
|
files: number;
|
|
1178
1192
|
contentHash?: string;
|
|
1179
1193
|
branch?: string;
|
|
1194
|
+
commit?: string;
|
|
1180
1195
|
prUrl?: string;
|
|
1196
|
+
preview?: PreviewMeta;
|
|
1197
|
+
/** 核心给出的跳过原因(merged / pr-closed / unchanged)。 */
|
|
1198
|
+
skipReason?: string;
|
|
1181
1199
|
error?: {
|
|
1182
1200
|
code: string;
|
|
1183
1201
|
message: string;
|
|
@@ -1185,6 +1203,8 @@ interface RunSummary {
|
|
|
1185
1203
|
area: string;
|
|
1186
1204
|
retriable: boolean;
|
|
1187
1205
|
inferred?: boolean;
|
|
1206
|
+
/** 机器可判定的细分原因(push 失败时由核心给出)。 */
|
|
1207
|
+
reason?: string;
|
|
1188
1208
|
};
|
|
1189
1209
|
warnings: string[];
|
|
1190
1210
|
logFile?: string;
|
|
@@ -1205,6 +1225,8 @@ interface RunnerDeps {
|
|
|
1205
1225
|
env?: NodeJS.ProcessEnv;
|
|
1206
1226
|
/** 运行结束(成功或失败)后回调,用于刷新调度/界面缓存。 */
|
|
1207
1227
|
onFinished?: (summary: RunSummary) => void;
|
|
1228
|
+
/** 预览副本这类「可失败但不阻断运行」的事件走这里,不写进 warnings。 */
|
|
1229
|
+
logger?: (event: string, data?: Record<string, unknown>) => void;
|
|
1208
1230
|
}
|
|
1209
1231
|
declare class Runner {
|
|
1210
1232
|
private active;
|
|
@@ -1218,6 +1240,15 @@ declare class Runner {
|
|
|
1218
1240
|
run(options: RunRequestOptions): Promise<RunSummary>;
|
|
1219
1241
|
private summarize;
|
|
1220
1242
|
private failureSummary;
|
|
1243
|
+
/**
|
|
1244
|
+
* 从本次核心返回的 artifact 清单里选当天的 `.md`,只读复制到 previews/。
|
|
1245
|
+
*
|
|
1246
|
+
* 这是插件**唯一**读取用户仓库的场景,而且只读核心自己刚写出的那一份:
|
|
1247
|
+
* - 路径必须在 `repo.path` 内(词法 + realpath 各判一次,挡 `..`/绝对路径/符号链接);
|
|
1248
|
+
* - 上限 512 KB、拒绝 NUL、sha256 必须与契约声明一致;
|
|
1249
|
+
* - 任何一步失败都只记日志,返回 undefined;运行结果不受影响。
|
|
1250
|
+
*/
|
|
1251
|
+
private capturePreview;
|
|
1221
1252
|
/** 写历史与状态;同一天的 skipped 运行不覆盖 lastDigest。 */
|
|
1222
1253
|
private record;
|
|
1223
1254
|
/** 给设置页/工具用的「核心现在什么状态」。 */
|
|
@@ -1233,6 +1264,22 @@ declare namespace runner {
|
|
|
1233
1264
|
export { type runner_RunRequestOptions as RunRequestOptions, type runner_RunSummary as RunSummary, runner_Runner as Runner, type runner_RunnerDeps as RunnerDeps };
|
|
1234
1265
|
}
|
|
1235
1266
|
|
|
1267
|
+
interface PluginInfo {
|
|
1268
|
+
name: string;
|
|
1269
|
+
version: string;
|
|
1270
|
+
repository?: string;
|
|
1271
|
+
}
|
|
1272
|
+
/** 只放行 GitHub 的 http(s) 地址;ssh / git+ 前缀先归一化。 */
|
|
1273
|
+
declare function normalizeRepositoryUrl(value: unknown): string | undefined;
|
|
1274
|
+
declare function readPluginInfo(): PluginInfo;
|
|
1275
|
+
|
|
1276
|
+
type pluginInfo_PluginInfo = PluginInfo;
|
|
1277
|
+
declare const pluginInfo_normalizeRepositoryUrl: typeof normalizeRepositoryUrl;
|
|
1278
|
+
declare const pluginInfo_readPluginInfo: typeof readPluginInfo;
|
|
1279
|
+
declare namespace pluginInfo {
|
|
1280
|
+
export { type pluginInfo_PluginInfo as PluginInfo, pluginInfo_normalizeRepositoryUrl as normalizeRepositoryUrl, pluginInfo_readPluginInfo as readPluginInfo };
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1236
1283
|
/**
|
|
1237
1284
|
* 调度(FR-P-08 / 规格 §9)。
|
|
1238
1285
|
*
|
|
@@ -1276,7 +1323,8 @@ declare class Scheduler {
|
|
|
1276
1323
|
private readonly deps;
|
|
1277
1324
|
private timerHandle;
|
|
1278
1325
|
private stopped;
|
|
1279
|
-
|
|
1326
|
+
/** 当天已尝试次数与时间;只在内存里,进程重启后重新计数。 */
|
|
1327
|
+
private readonly attempts;
|
|
1280
1328
|
constructor(deps: SchedulerDeps);
|
|
1281
1329
|
start(): void;
|
|
1282
1330
|
stop(): void;
|
|
@@ -1329,6 +1377,9 @@ interface RouteDeps {
|
|
|
1329
1377
|
/** 注入用:pendingPr 对账的 fetch 实现(测试不联网)。 */
|
|
1330
1378
|
fetchImpl?: typeof fetch;
|
|
1331
1379
|
version: string;
|
|
1380
|
+
/** 包名与仓库地址都来自 package.json,由 src/index.ts 传入。 */
|
|
1381
|
+
pluginName?: string;
|
|
1382
|
+
repository?: string;
|
|
1332
1383
|
logger?: (event: string, data?: Record<string, unknown>) => void;
|
|
1333
1384
|
}
|
|
1334
1385
|
declare function createRoutes(deps: RouteDeps): (request: http.IncomingMessage, response: http.ServerResponse) => Promise<void>;
|
|
@@ -1582,4 +1633,4 @@ declare namespace tools {
|
|
|
1582
1633
|
export { tools_createTrendingHistoryTool as createTrendingHistoryTool, tools_createTrendingRunTool as createTrendingRunTool };
|
|
1583
1634
|
}
|
|
1584
1635
|
|
|
1585
|
-
export { channel, config, core, credentials, errors, exec, github, handshake$1 as handshake, importCli, ipc, locate, paths, pendingPr, preflight, proxy, quickfill, release, repo, routes, run, runner, schedule, state, tools };
|
|
1636
|
+
export { channel, config, core, credentials, errors, exec, github, handshake$1 as handshake, importCli, ipc, locate, paths, pendingPr, pluginInfo, preflight, proxy, quickfill, release, repo, routes, run, runner, schedule, state, tools };
|
package/lib/internal.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
locate_exports,
|
|
12
12
|
paths_exports,
|
|
13
13
|
pending_pr_exports,
|
|
14
|
+
plugin_info_exports,
|
|
14
15
|
preflight_exports,
|
|
15
16
|
proxy_exports,
|
|
16
17
|
quickfill_exports,
|
|
@@ -22,7 +23,7 @@ import {
|
|
|
22
23
|
schedule_exports,
|
|
23
24
|
state_exports,
|
|
24
25
|
tools_exports
|
|
25
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-X4PBXIZE.js";
|
|
26
27
|
import {
|
|
27
28
|
exec_exports
|
|
28
29
|
} from "./chunk-XLIFTAVL.js";
|
|
@@ -40,6 +41,7 @@ export {
|
|
|
40
41
|
locate_exports as locate,
|
|
41
42
|
paths_exports as paths,
|
|
42
43
|
pending_pr_exports as pendingPr,
|
|
44
|
+
plugin_info_exports as pluginInfo,
|
|
43
45
|
preflight_exports as preflight,
|
|
44
46
|
proxy_exports as proxy,
|
|
45
47
|
quickfill_exports as quickfill,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morehao/dsh-github-trending",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "GitHub 趋势日报插件:每天定时 spawn 本地 trendradar 核心,抓取 Trending、生成中文简介与总结,提交到你的仓库并自动合并 PR。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -14,13 +14,11 @@
|
|
|
14
14
|
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
15
15
|
"./locale/*.json": "./locale/*.json",
|
|
16
16
|
"./icon": "./icon.svg",
|
|
17
|
-
"./contract/*": "./contract/*",
|
|
18
17
|
"./package.json": "./package.json"
|
|
19
18
|
},
|
|
20
19
|
"files": [
|
|
21
20
|
"lib",
|
|
22
21
|
"client.js",
|
|
23
|
-
"contract",
|
|
24
22
|
"locale",
|
|
25
23
|
"cordis.patch.yml",
|
|
26
24
|
"icon.svg",
|
|
@@ -51,13 +49,16 @@
|
|
|
51
49
|
"optional": true
|
|
52
50
|
}
|
|
53
51
|
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@morehao/trendradar-contract": "0.2.0"
|
|
54
|
+
},
|
|
54
55
|
"dsh": {
|
|
55
56
|
"bundle": {
|
|
56
57
|
"patch": "./cordis.patch.yml"
|
|
57
58
|
},
|
|
58
59
|
"core": {
|
|
59
60
|
"repo": "morehao/trendradar",
|
|
60
|
-
"version": "0.
|
|
61
|
+
"version": "0.3.1"
|
|
61
62
|
},
|
|
62
63
|
"client": {
|
|
63
64
|
"platform": "web",
|
|
@@ -75,8 +76,8 @@
|
|
|
75
76
|
"dev:client": "node scripts/build-client.mjs --watch",
|
|
76
77
|
"typecheck": "tsc --noEmit",
|
|
77
78
|
"check:types": "tsc -p tsconfig.client.json",
|
|
78
|
-
"
|
|
79
|
-
"check:contract": "node
|
|
79
|
+
"set-core-version": "node scripts/set-core-version.mjs",
|
|
80
|
+
"check:contract": "node --test test/contract.test.mjs",
|
|
80
81
|
"check:lockfile": "node scripts/check-lockfile.mjs",
|
|
81
82
|
"check:syntax": "node scripts/check-syntax.mjs",
|
|
82
83
|
"check:naming": "node scripts/check-naming.mjs",
|
package/contract/CONTRACT.sha256
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# trendradar contract hash (v1)
|
|
2
|
-
# algorithm: sha256 per file (sorted), then sha256 of the concatenated lines
|
|
3
|
-
f34aa3642938bad32df912768d543925f91858da66a8e726e4e4bf88a07d086c fixtures/digest-degraded.json
|
|
4
|
-
7bc67dcdc36c7b81df1915c4b50025cef6f0304ef2feb24f393d17771df252c9 fixtures/digest-empty-lang.json
|
|
5
|
-
4fe9d91ce3f0ddcc17982e4f0acac950ebc6ec7f63427fe962ea581f3114c0c4 fixtures/digest-normal.json
|
|
6
|
-
d52adbbb473d7ee2d856398a5651f96ba2019ee53ba6e14c27ef2d792b5db723 schema/api.v1.yaml
|
|
7
|
-
ca9b640f374cded12a09ea00f0686eac14eee59eef94c27a61016175bc0d4ebb schema/digest.v1.json
|
|
8
|
-
cf8eb4f069e71c8738ccd5c4ffbfb266124ee86161fe8ad0677c9782d3ef8552 schema/ipc.v1.json
|
|
9
|
-
2140c9d538bc935c092a83fb6377e3878b2c060992bf458f2437898f4649d41d schema/model.v1.json
|
|
10
|
-
aggregate: 4717074d26d1a6d85c1885bd052d411f81c20b39467cb81c398f565bbe34dcde
|
package/contract/UPSTREAM.json
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"repo": "https://github.com/morehao/trendradar.git",
|
|
3
|
-
"ref": "v0.2.0",
|
|
4
|
-
"commit": "5cf0ad26c5748cf2bc9a1cc4ce66a09411d590f7",
|
|
5
|
-
"aggregate": "4717074d26d1a6d85c1885bd052d411f81c20b39467cb81c398f565bbe34dcde",
|
|
6
|
-
"syncedAt": "2026-10-30T18:42:04.265Z",
|
|
7
|
-
"files": [
|
|
8
|
-
{
|
|
9
|
-
"path": "schema/api.v1.yaml",
|
|
10
|
-
"sha256": "d52adbbb473d7ee2d856398a5651f96ba2019ee53ba6e14c27ef2d792b5db723"
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"path": "schema/digest.v1.json",
|
|
14
|
-
"sha256": "ca9b640f374cded12a09ea00f0686eac14eee59eef94c27a61016175bc0d4ebb"
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"path": "schema/ipc.v1.json",
|
|
18
|
-
"sha256": "cf8eb4f069e71c8738ccd5c4ffbfb266124ee86161fe8ad0677c9782d3ef8552"
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"path": "schema/model.v1.json",
|
|
22
|
-
"sha256": "2140c9d538bc935c092a83fb6377e3878b2c060992bf458f2437898f4649d41d"
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"path": "fixtures/digest-degraded.json",
|
|
26
|
-
"sha256": "f34aa3642938bad32df912768d543925f91858da66a8e726e4e4bf88a07d086c"
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"path": "fixtures/digest-empty-lang.json",
|
|
30
|
-
"sha256": "7bc67dcdc36c7b81df1915c4b50025cef6f0304ef2feb24f393d17771df252c9"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"path": "fixtures/digest-normal.json",
|
|
34
|
-
"sha256": "4fe9d91ce3f0ddcc17982e4f0acac950ebc6ec7f63427fe962ea581f3114c0c4"
|
|
35
|
-
}
|
|
36
|
-
]
|
|
37
|
-
}
|