@morehao/dsh-github-trending 0.2.0 → 0.3.1

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