dsh-config-manager 0.1.19 → 0.1.21
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/client.d.ts +319 -2
- package/lib/client.js +1271 -109
- package/lib/client.js.map +1 -1
- package/lib/core/run-registry.d.ts +2 -2
- package/lib/core/run-registry.js +5 -1
- package/lib/core/run-registry.js.map +1 -1
- package/lib/index.js +408 -2
- package/lib/index.js.map +1 -1
- package/lib/sync/ancestor.d.ts +17 -0
- package/lib/sync/ancestor.js +64 -0
- package/lib/sync/ancestor.js.map +1 -0
- package/lib/sync/autosync-config.d.ts +34 -0
- package/lib/sync/autosync-config.js +116 -0
- package/lib/sync/autosync-config.js.map +1 -0
- package/lib/sync/autosync-scheduler.d.ts +95 -0
- package/lib/sync/autosync-scheduler.js +379 -0
- package/lib/sync/autosync-scheduler.js.map +1 -0
- package/lib/sync/merge.d.ts +42 -0
- package/lib/sync/merge.js +325 -0
- package/lib/sync/merge.js.map +1 -0
- package/lib/sync/review-queue.d.ts +51 -0
- package/lib/sync/review-queue.js +119 -0
- package/lib/sync/review-queue.js.map +1 -0
- package/lib/sync/risk.d.ts +59 -0
- package/lib/sync/risk.js +76 -0
- package/lib/sync/risk.js.map +1 -0
- package/lib/sync/sync-config.d.ts +7 -2
- package/lib/sync/sync-config.js +24 -4
- package/lib/sync/sync-config.js.map +1 -1
- package/lib/sync/sync-engine.d.ts +88 -2
- package/lib/sync/sync-engine.js +333 -8
- package/lib/sync/sync-engine.js.map +1 -1
- package/lib/sync/sync-history.d.ts +39 -0
- package/lib/sync/sync-history.js +88 -0
- package/lib/sync/sync-history.js.map +1 -0
- package/lib/sync/sync-session.d.ts +39 -0
- package/lib/sync/sync-session.js +54 -0
- package/lib/sync/sync-session.js.map +1 -0
- package/lib/sync/sync-state.d.ts +7 -2
- package/lib/sync/sync-state.js +9 -5
- package/lib/sync/sync-state.js.map +1 -1
- package/lib/ui/i18n.d.ts +63 -0
- package/lib/ui/i18n.js +130 -1
- package/lib/ui/i18n.js.map +1 -1
- package/package.json +1 -1
- package/src/client/config-manager.module.css +18 -0
- package/src/client/sync/SyncConfirmView.tsx +301 -0
- package/src/client/sync/SyncHistoryView.test.ts +40 -0
- package/src/client/sync/SyncHistoryView.tsx +155 -0
- package/src/client/sync/SyncSettingsView.tsx +241 -12
- package/src/client/sync/history-model.test.ts +82 -0
- package/src/client/sync/history-model.ts +112 -0
- package/src/client/sync/sync-api.test.ts +155 -0
- package/src/client/sync/sync-api.ts +219 -0
- package/src/client/sync/sync-locales.ts +162 -1
- package/src/client/sync/sync-view-v2.test.ts +131 -0
- package/src/client/sync/sync-view.ts +151 -4
- package/src/core/run-registry.ts +7 -3
- package/src/index.ts +420 -3
- package/src/sync/ancestor.test.ts +152 -0
- package/src/sync/ancestor.ts +81 -0
- package/src/sync/autosync-config.test.ts +93 -0
- package/src/sync/autosync-config.ts +137 -0
- package/src/sync/autosync-scheduler.test.ts +191 -0
- package/src/sync/autosync-scheduler.ts +443 -0
- package/src/sync/merge.test.ts +204 -0
- package/src/sync/merge.ts +360 -0
- package/src/sync/review-queue.test.ts +120 -0
- package/src/sync/review-queue.ts +167 -0
- package/src/sync/risk.test.ts +131 -0
- package/src/sync/risk.ts +122 -0
- package/src/sync/sync-config.test.ts +106 -0
- package/src/sync/sync-config.ts +23 -4
- package/src/sync/sync-engine.test.ts +392 -3
- package/src/sync/sync-engine.ts +383 -10
- package/src/sync/sync-history.test.ts +85 -0
- package/src/sync/sync-history.ts +126 -0
- package/src/sync/sync-session.test.ts +137 -0
- package/src/sync/sync-session.ts +76 -0
- package/src/sync/sync-state.test.ts +48 -2
- package/src/sync/sync-state.ts +11 -5
- package/src/ui/i18n.ts +132 -3
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Logger } from '../utils/logger.ts';
|
|
2
|
+
import type { MsgFunc } from '../core/messages.ts';
|
|
3
|
+
import type { SectionId } from '../schema/types.ts';
|
|
4
|
+
import type { RunRegistry } from '../core/run-registry.ts';
|
|
5
|
+
import type { SyncEngine } from './sync-engine.ts';
|
|
6
|
+
import type { AutosyncConfig, AutosyncInterval } from './autosync-config.ts';
|
|
7
|
+
import type { SyncConfig } from './sync-config.ts';
|
|
8
|
+
import { readSyncHistory } from './sync-history.ts';
|
|
9
|
+
import type { AutosyncHistoryEntry } from './sync-history.ts';
|
|
10
|
+
import type { MergePlan } from './merge.ts';
|
|
11
|
+
import type { SyncApplyPlan } from './risk.ts';
|
|
12
|
+
/** 间隔 → ms 换算(§4.3) */
|
|
13
|
+
export declare function intervalToMs(interval: AutosyncInterval): number;
|
|
14
|
+
/**
|
|
15
|
+
* 启动触发下载合并且满足阈值(now - lastRunAt >= startupMinIntervalMs)?
|
|
16
|
+
* lastRunAt 为 undefined(从未运行)→ true。
|
|
17
|
+
*/
|
|
18
|
+
export declare function shouldTriggerStartupRun(lastRunAt: string | undefined, startupMinIntervalMs: number, nowMs?: number): boolean;
|
|
19
|
+
/** runOnce 执行结果 */
|
|
20
|
+
export interface AutosyncRunResult {
|
|
21
|
+
status: 'success' | 'skipped' | 'failed' | 'partial';
|
|
22
|
+
direction: 'pull' | 'push' | 'both' | 'none';
|
|
23
|
+
skipReason?: string;
|
|
24
|
+
conflictedSections?: SectionId[];
|
|
25
|
+
appliedSections?: SectionId[];
|
|
26
|
+
pushedSnapshotId?: string;
|
|
27
|
+
pulledSnapshotId?: string;
|
|
28
|
+
error?: string;
|
|
29
|
+
historyId: string;
|
|
30
|
+
consecutiveFailures: number;
|
|
31
|
+
}
|
|
32
|
+
export interface AutoSyncSchedulerOptions {
|
|
33
|
+
syncDir: string;
|
|
34
|
+
host: {
|
|
35
|
+
log: Logger;
|
|
36
|
+
};
|
|
37
|
+
makeSyncEngine: (repoUrl: string, gitBin?: string) => SyncEngine;
|
|
38
|
+
/** 消息翻译器 */
|
|
39
|
+
msg: MsgFunc;
|
|
40
|
+
runs: RunRegistry;
|
|
41
|
+
/** 时间源(测试注入) */
|
|
42
|
+
now?: () => Date;
|
|
43
|
+
/** 注入 autosync-config 读写(测试可内存实现) */
|
|
44
|
+
readConfig?: () => Promise<AutosyncConfig>;
|
|
45
|
+
writeConfig?: (cfg: AutosyncConfig) => Promise<void>;
|
|
46
|
+
/** 注入 sync-config 读取 */
|
|
47
|
+
readSyncConfigFn?: () => Promise<SyncConfig | null>;
|
|
48
|
+
/** 注入 sync-history 读写 */
|
|
49
|
+
readHistoryFn?: () => Promise<Awaited<ReturnType<typeof readSyncHistory>>>;
|
|
50
|
+
appendHistoryFn?: (entry: AutosyncHistoryEntry) => Promise<void>;
|
|
51
|
+
/** 注入计时器(测试用;缺省 setInterval/clearInterval) */
|
|
52
|
+
setTimer?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;
|
|
53
|
+
clearTimer?: (timer: ReturnType<typeof setTimeout>) => void;
|
|
54
|
+
}
|
|
55
|
+
export declare class AutoSyncScheduler {
|
|
56
|
+
private readonly syncDir;
|
|
57
|
+
private readonly host;
|
|
58
|
+
private readonly makeSyncEngine;
|
|
59
|
+
private readonly msg;
|
|
60
|
+
private readonly runs;
|
|
61
|
+
private readonly now;
|
|
62
|
+
private readonly readConfig;
|
|
63
|
+
private readonly writeConfig;
|
|
64
|
+
private readonly readSyncConfigFn;
|
|
65
|
+
private readonly readHistoryFn;
|
|
66
|
+
private readonly appendHistoryFn;
|
|
67
|
+
private readonly setTimer;
|
|
68
|
+
private readonly clearTimer;
|
|
69
|
+
private timer;
|
|
70
|
+
private stopped;
|
|
71
|
+
private running;
|
|
72
|
+
constructor(opts: AutoSyncSchedulerOptions);
|
|
73
|
+
/** 启动:读配置 → 若 enabled 启动定时器 → 无条件执行一次启动触发下载合并。 */
|
|
74
|
+
start(): void;
|
|
75
|
+
/** 停止:清定时器、标记不再调度;正在执行的任务允许自然结束。 */
|
|
76
|
+
stop(): void;
|
|
77
|
+
/** 重新加载配置(路由 POST /sync/autosync 后调用)。 */
|
|
78
|
+
reload(): Promise<void>;
|
|
79
|
+
private refreshTimer;
|
|
80
|
+
/** 启动触发下载合并(受 startupMinIntervalMs 阈值约束)。 */
|
|
81
|
+
private startupRun;
|
|
82
|
+
/**
|
|
83
|
+
* 执行一次自动同步(§6.1)。
|
|
84
|
+
* @param opts.startup - true 表示启动触发变体(只做 Phase A+B,不做 Phase C push)
|
|
85
|
+
*/
|
|
86
|
+
runOnce(opts?: {
|
|
87
|
+
startup?: boolean;
|
|
88
|
+
}): Promise<AutosyncRunResult>;
|
|
89
|
+
/** 收尾:写 autosync-config(lastRunAt, lastRunStatus, consecutiveFailures, lastRunHistoryId)。 */
|
|
90
|
+
private writeFinalConfig;
|
|
91
|
+
/** 连续失败 ≥ 3 → 通知(host.log.warn)。 */
|
|
92
|
+
private maybeNotify;
|
|
93
|
+
}
|
|
94
|
+
/** 从 MergePlan 构造 SyncApplyPlan(autoApply = 所有非 skip 非 conflict 项)。 */
|
|
95
|
+
export declare function buildAutoApplyPlan(plan: MergePlan): SyncApplyPlan;
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutoSyncScheduler:宿主后台自动同步调度器。
|
|
3
|
+
*
|
|
4
|
+
* 生命周期:
|
|
5
|
+
* - start():读 autosync-config;若 enabled 启动定时器(按 interval);无条件执行一次
|
|
6
|
+
* 「启动触发下载合并」(受 startupMinIntervalMs 阈值约束)。
|
|
7
|
+
* - stop():清定时器、标记不再调度。
|
|
8
|
+
* - runOnce():执行一次完整双向自动同步(§6.1 流程)。
|
|
9
|
+
*
|
|
10
|
+
* 核心逻辑(runOnce):
|
|
11
|
+
* - 读配置;若 !enabled → return
|
|
12
|
+
* - runs.register('autosync') 防重复;同 kind running → 跳过
|
|
13
|
+
* - readSyncConfig → repoUrl 无 → 记 skipped(未配置) → return
|
|
14
|
+
* - Phase A: engine.merge() 三方合并 → 判定 needsReview(冲突/缺失依赖/Install/Error)
|
|
15
|
+
* - 冲突 → 跳过 + 写历史 skipped + conflictedSections[] → return
|
|
16
|
+
* - Phase B: 无冲突 → engine.applyMergePlan(apply) 写入本地
|
|
17
|
+
* - Phase C: 完整双向 → engine.push() 上传
|
|
18
|
+
* - 收尾:写 autosync-config(lastRunAt, lastRunStatus, consecutiveFailures, lastRunHistoryId)
|
|
19
|
+
*
|
|
20
|
+
* 连续失败计数:只对网络/传输/apply 真实失败计数;skipped(未配置/冲突跳过/无远端)不计。
|
|
21
|
+
* 连续失败 ≥ 3 → host.log.warn 通知 + 记 notifiedAt。
|
|
22
|
+
*/
|
|
23
|
+
import crypto from 'node:crypto';
|
|
24
|
+
import { readAutosyncConfig, writeAutosyncConfig } from "./autosync-config.js";
|
|
25
|
+
import { readSyncConfig } from "./sync-config.js";
|
|
26
|
+
import { readSyncHistory, appendAutosyncEntry } from "./sync-history.js";
|
|
27
|
+
/** 间隔 → ms 换算(§4.3) */
|
|
28
|
+
export function intervalToMs(interval) {
|
|
29
|
+
const table = {
|
|
30
|
+
'5m': 5 * 60 * 1000,
|
|
31
|
+
'15m': 15 * 60 * 1000,
|
|
32
|
+
'30m': 30 * 60 * 1000,
|
|
33
|
+
'60m': 60 * 60 * 1000,
|
|
34
|
+
'6h': 6 * 60 * 60 * 1000,
|
|
35
|
+
'12h': 12 * 60 * 60 * 1000,
|
|
36
|
+
'24h': 24 * 60 * 60 * 1000,
|
|
37
|
+
};
|
|
38
|
+
return table[interval];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 启动触发下载合并且满足阈值(now - lastRunAt >= startupMinIntervalMs)?
|
|
42
|
+
* lastRunAt 为 undefined(从未运行)→ true。
|
|
43
|
+
*/
|
|
44
|
+
export function shouldTriggerStartupRun(lastRunAt, startupMinIntervalMs, nowMs = Date.now()) {
|
|
45
|
+
if (lastRunAt === undefined || lastRunAt === '')
|
|
46
|
+
return true;
|
|
47
|
+
const lastMs = Date.parse(lastRunAt);
|
|
48
|
+
if (Number.isNaN(lastMs))
|
|
49
|
+
return true;
|
|
50
|
+
return nowMs - lastMs >= startupMinIntervalMs;
|
|
51
|
+
}
|
|
52
|
+
export class AutoSyncScheduler {
|
|
53
|
+
syncDir;
|
|
54
|
+
host;
|
|
55
|
+
makeSyncEngine;
|
|
56
|
+
msg;
|
|
57
|
+
runs;
|
|
58
|
+
now;
|
|
59
|
+
readConfig;
|
|
60
|
+
writeConfig;
|
|
61
|
+
readSyncConfigFn;
|
|
62
|
+
readHistoryFn;
|
|
63
|
+
appendHistoryFn;
|
|
64
|
+
setTimer;
|
|
65
|
+
clearTimer;
|
|
66
|
+
timer = null;
|
|
67
|
+
stopped = false;
|
|
68
|
+
running = false;
|
|
69
|
+
constructor(opts) {
|
|
70
|
+
this.syncDir = opts.syncDir;
|
|
71
|
+
this.host = opts.host;
|
|
72
|
+
this.makeSyncEngine = opts.makeSyncEngine;
|
|
73
|
+
this.msg = opts.msg;
|
|
74
|
+
this.runs = opts.runs;
|
|
75
|
+
this.now = opts.now ?? (() => new Date());
|
|
76
|
+
this.readConfig = opts.readConfig ?? (() => readAutosyncConfig(this.syncDir));
|
|
77
|
+
this.writeConfig = opts.writeConfig ?? ((cfg) => writeAutosyncConfig(this.syncDir, cfg));
|
|
78
|
+
this.readSyncConfigFn = opts.readSyncConfigFn ?? (() => readSyncConfig(this.syncDir));
|
|
79
|
+
this.readHistoryFn = opts.readHistoryFn ?? (() => readSyncHistory(this.syncDir));
|
|
80
|
+
this.appendHistoryFn = opts.appendHistoryFn ?? ((entry) => appendAutosyncEntry(this.syncDir, entry));
|
|
81
|
+
this.setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
82
|
+
this.clearTimer = opts.clearTimer ?? ((t) => clearTimeout(t));
|
|
83
|
+
}
|
|
84
|
+
/** 启动:读配置 → 若 enabled 启动定时器 → 无条件执行一次启动触发下载合并。 */
|
|
85
|
+
start() {
|
|
86
|
+
if (this.stopped)
|
|
87
|
+
return;
|
|
88
|
+
this.refreshTimer();
|
|
89
|
+
void this.startupRun();
|
|
90
|
+
}
|
|
91
|
+
/** 停止:清定时器、标记不再调度;正在执行的任务允许自然结束。 */
|
|
92
|
+
stop() {
|
|
93
|
+
this.stopped = true;
|
|
94
|
+
if (this.timer !== null) {
|
|
95
|
+
this.clearTimer(this.timer);
|
|
96
|
+
this.timer = null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** 重新加载配置(路由 POST /sync/autosync 后调用)。 */
|
|
100
|
+
async reload() {
|
|
101
|
+
if (this.stopped)
|
|
102
|
+
return;
|
|
103
|
+
this.refreshTimer();
|
|
104
|
+
}
|
|
105
|
+
refreshTimer() {
|
|
106
|
+
if (this.timer !== null) {
|
|
107
|
+
this.clearTimer(this.timer);
|
|
108
|
+
this.timer = null;
|
|
109
|
+
}
|
|
110
|
+
void this.readConfig().then((cfg) => {
|
|
111
|
+
if (this.stopped || !cfg.enabled)
|
|
112
|
+
return;
|
|
113
|
+
const ms = intervalToMs(cfg.interval);
|
|
114
|
+
this.timer = this.setTimer(() => {
|
|
115
|
+
if (this.stopped)
|
|
116
|
+
return;
|
|
117
|
+
void this.runOnce().catch((err) => {
|
|
118
|
+
this.host.log.error('自动同步定时触发失败', { error: err instanceof Error ? err.message : String(err) });
|
|
119
|
+
});
|
|
120
|
+
}, ms);
|
|
121
|
+
}).catch(() => { });
|
|
122
|
+
}
|
|
123
|
+
/** 启动触发下载合并(受 startupMinIntervalMs 阈值约束)。 */
|
|
124
|
+
async startupRun() {
|
|
125
|
+
try {
|
|
126
|
+
const cfg = await this.readConfig();
|
|
127
|
+
if (!cfg.enabled)
|
|
128
|
+
return; // 总开关关闭 → 启动触发不执行
|
|
129
|
+
if (!shouldTriggerStartupRun(cfg.lastRunAt, cfg.startupMinIntervalMs, this.now().getTime())) {
|
|
130
|
+
this.host.log.info('自动同步启动触发跳过:距上次运行未达阈值');
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
await this.runOnce({ startup: true });
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
this.host.log.error('启动触发下载合并失败', { error: err instanceof Error ? err.message : String(err) });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 执行一次自动同步(§6.1)。
|
|
141
|
+
* @param opts.startup - true 表示启动触发变体(只做 Phase A+B,不做 Phase C push)
|
|
142
|
+
*/
|
|
143
|
+
async runOnce(opts = {}) {
|
|
144
|
+
if (this.running)
|
|
145
|
+
return { status: 'skipped', direction: 'none', skipReason: 'running', historyId: '', consecutiveFailures: 0 };
|
|
146
|
+
const cfg = await this.readConfig();
|
|
147
|
+
if (!cfg.enabled) {
|
|
148
|
+
return { status: 'skipped', direction: 'none', skipReason: 'disabled', historyId: '', consecutiveFailures: cfg.consecutiveFailures };
|
|
149
|
+
}
|
|
150
|
+
this.running = true;
|
|
151
|
+
const nowIso = this.now().toISOString();
|
|
152
|
+
const historyId = `autosync-${crypto.randomUUID()}`;
|
|
153
|
+
// runs 防重复:同 kind running → 跳过(内部语义,不打搅用户)
|
|
154
|
+
let runId = null;
|
|
155
|
+
try {
|
|
156
|
+
const run = this.runs.register('autosync');
|
|
157
|
+
runId = run.runId;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
this.running = false;
|
|
161
|
+
return { status: 'skipped', direction: 'none', skipReason: 'conflict', historyId, consecutiveFailures: cfg.consecutiveFailures };
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
// 读 sync-config → repoUrl
|
|
165
|
+
const syncCfg = await this.readSyncConfigFn();
|
|
166
|
+
if (syncCfg === null || syncCfg.repoUrl === '') {
|
|
167
|
+
const result = {
|
|
168
|
+
status: 'skipped', direction: 'none', skipReason: 'unconfigured', historyId,
|
|
169
|
+
consecutiveFailures: cfg.consecutiveFailures,
|
|
170
|
+
};
|
|
171
|
+
await this.appendHistoryFn({
|
|
172
|
+
direction: 'both',
|
|
173
|
+
status: 'skipped',
|
|
174
|
+
skipReason: 'unconfigured',
|
|
175
|
+
createdAt: nowIso,
|
|
176
|
+
failureCountAtRun: cfg.consecutiveFailures,
|
|
177
|
+
});
|
|
178
|
+
await this.writeFinalConfig(cfg, result, nowIso, historyId);
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
const engine = this.makeSyncEngine(syncCfg.repoUrl, syncCfg.gitBin);
|
|
182
|
+
// Phase A: pull 合并(下载)
|
|
183
|
+
let mergePlan;
|
|
184
|
+
try {
|
|
185
|
+
mergePlan = await engine.merge();
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
189
|
+
const result = {
|
|
190
|
+
status: 'failed', direction: 'pull', error, historyId,
|
|
191
|
+
consecutiveFailures: cfg.consecutiveFailures + 1,
|
|
192
|
+
};
|
|
193
|
+
await this.appendHistoryFn({
|
|
194
|
+
direction: 'pull', status: 'failed', error, createdAt: nowIso,
|
|
195
|
+
failureCountAtRun: cfg.consecutiveFailures + 1,
|
|
196
|
+
});
|
|
197
|
+
await this.writeFinalConfig(cfg, result, nowIso, historyId);
|
|
198
|
+
this.maybeNotify(cfg.consecutiveFailures + 1, historyId, nowIso);
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
// 判定 needsReview
|
|
202
|
+
const reviewSections = mergePlan.sections.filter((s) => s.decision === 'conflict');
|
|
203
|
+
if (reviewSections.length > 0) {
|
|
204
|
+
const conflictedSections = reviewSections.map((s) => s.id);
|
|
205
|
+
const result = {
|
|
206
|
+
status: 'skipped', direction: 'pull', skipReason: 'conflict',
|
|
207
|
+
conflictedSections, historyId,
|
|
208
|
+
consecutiveFailures: cfg.consecutiveFailures,
|
|
209
|
+
};
|
|
210
|
+
await this.appendHistoryFn({
|
|
211
|
+
direction: 'pull', status: 'skipped', skipReason: 'conflict',
|
|
212
|
+
conflictedSections, createdAt: nowIso,
|
|
213
|
+
failureCountAtRun: cfg.consecutiveFailures,
|
|
214
|
+
});
|
|
215
|
+
await this.writeFinalConfig(cfg, result, nowIso, historyId);
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
// 无冲突:构造 SyncApplyPlan(autoApply = 所有 useRemote/keepLocal 项;skipped = skip 项)
|
|
219
|
+
const apply = buildAutoApplyPlan(mergePlan);
|
|
220
|
+
if (apply.autoApply.length === 0) {
|
|
221
|
+
// 无物可应用(全部 skip / 无变化)
|
|
222
|
+
const result = {
|
|
223
|
+
status: 'success', direction: 'pull', skipReason: 'unchanged', historyId,
|
|
224
|
+
consecutiveFailures: 0,
|
|
225
|
+
};
|
|
226
|
+
await this.appendHistoryFn({
|
|
227
|
+
direction: 'pull', status: 'success', skipReason: 'unchanged',
|
|
228
|
+
createdAt: nowIso, failureCountAtRun: 0,
|
|
229
|
+
});
|
|
230
|
+
await this.writeFinalConfig(cfg, result, nowIso, historyId);
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
// Phase B: 写入本地(applyMergePlan,无 review-queue 写)
|
|
234
|
+
const applyReport = await engine.applyMergePlan(apply);
|
|
235
|
+
const appliedSections = applyReport.applied;
|
|
236
|
+
if (!applyReport.ok) {
|
|
237
|
+
const error = applyReport.warnings.join('; ') || 'applyMergePlan 执行失败';
|
|
238
|
+
const result = {
|
|
239
|
+
status: 'failed', direction: 'pull', error, historyId,
|
|
240
|
+
consecutiveFailures: cfg.consecutiveFailures + 1,
|
|
241
|
+
};
|
|
242
|
+
await this.appendHistoryFn({
|
|
243
|
+
direction: 'pull', status: 'failed', error, createdAt: nowIso,
|
|
244
|
+
failureCountAtRun: cfg.consecutiveFailures + 1,
|
|
245
|
+
});
|
|
246
|
+
await this.writeFinalConfig(cfg, result, nowIso, historyId);
|
|
247
|
+
this.maybeNotify(cfg.consecutiveFailures + 1, historyId, nowIso);
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
// Phase C: push 上传(完整双向)
|
|
251
|
+
if (!opts.startup) {
|
|
252
|
+
try {
|
|
253
|
+
const pushReport = await engine.push();
|
|
254
|
+
if (!pushReport.ok) {
|
|
255
|
+
const error = pushReport.message ?? 'push 失败';
|
|
256
|
+
const result = {
|
|
257
|
+
status: 'failed', direction: 'both', appliedSections, error, historyId,
|
|
258
|
+
consecutiveFailures: cfg.consecutiveFailures + 1,
|
|
259
|
+
};
|
|
260
|
+
await this.appendHistoryFn({
|
|
261
|
+
direction: 'both', status: 'failed', appliedSections, error,
|
|
262
|
+
createdAt: nowIso, failureCountAtRun: cfg.consecutiveFailures + 1,
|
|
263
|
+
});
|
|
264
|
+
await this.writeFinalConfig(cfg, result, nowIso, historyId);
|
|
265
|
+
this.maybeNotify(cfg.consecutiveFailures + 1, historyId, nowIso);
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
const result = {
|
|
269
|
+
status: 'success', direction: 'both', appliedSections,
|
|
270
|
+
pushedSnapshotId: pushReport.snapshotId, historyId, consecutiveFailures: 0,
|
|
271
|
+
};
|
|
272
|
+
await this.appendHistoryFn({
|
|
273
|
+
direction: 'both', status: 'success', appliedSections,
|
|
274
|
+
pushedSnapshotId: pushReport.snapshotId, createdAt: nowIso, failureCountAtRun: 0,
|
|
275
|
+
});
|
|
276
|
+
await this.writeFinalConfig(cfg, result, nowIso, historyId);
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
281
|
+
const result = {
|
|
282
|
+
status: 'failed', direction: 'both', appliedSections, error, historyId,
|
|
283
|
+
consecutiveFailures: cfg.consecutiveFailures + 1,
|
|
284
|
+
};
|
|
285
|
+
await this.appendHistoryFn({
|
|
286
|
+
direction: 'both', status: 'failed', appliedSections, error,
|
|
287
|
+
createdAt: nowIso, failureCountAtRun: cfg.consecutiveFailures + 1,
|
|
288
|
+
});
|
|
289
|
+
await this.writeFinalConfig(cfg, result, nowIso, historyId);
|
|
290
|
+
this.maybeNotify(cfg.consecutiveFailures + 1, historyId, nowIso);
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// startup 变体:只做 pull 合并(不上传)
|
|
295
|
+
const result = {
|
|
296
|
+
status: 'success', direction: 'pull', appliedSections, historyId, consecutiveFailures: 0,
|
|
297
|
+
};
|
|
298
|
+
await this.appendHistoryFn({
|
|
299
|
+
direction: 'pull', status: 'success', appliedSections,
|
|
300
|
+
createdAt: nowIso, failureCountAtRun: 0,
|
|
301
|
+
});
|
|
302
|
+
await this.writeFinalConfig(cfg, result, nowIso, historyId);
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
catch (err) {
|
|
306
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
307
|
+
const result = {
|
|
308
|
+
status: 'failed', direction: 'none', error, historyId,
|
|
309
|
+
consecutiveFailures: cfg.consecutiveFailures + 1,
|
|
310
|
+
};
|
|
311
|
+
await this.appendHistoryFn({
|
|
312
|
+
direction: 'both', status: 'failed', error,
|
|
313
|
+
createdAt: nowIso, failureCountAtRun: cfg.consecutiveFailures + 1,
|
|
314
|
+
});
|
|
315
|
+
await this.writeFinalConfig(cfg, result, nowIso, historyId);
|
|
316
|
+
this.maybeNotify(cfg.consecutiveFailures + 1, historyId, nowIso);
|
|
317
|
+
return result;
|
|
318
|
+
}
|
|
319
|
+
finally {
|
|
320
|
+
this.running = false;
|
|
321
|
+
if (runId !== null) {
|
|
322
|
+
// 完成标记(不抛错,尽力而为)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/** 收尾:写 autosync-config(lastRunAt, lastRunStatus, consecutiveFailures, lastRunHistoryId)。 */
|
|
327
|
+
async writeFinalConfig(base, result, nowIso, historyId) {
|
|
328
|
+
await this.writeConfig({
|
|
329
|
+
...base,
|
|
330
|
+
lastRunAt: nowIso,
|
|
331
|
+
lastRunStatus: result.status,
|
|
332
|
+
consecutiveFailures: result.consecutiveFailures,
|
|
333
|
+
lastRunHistoryId: historyId,
|
|
334
|
+
...(result.error !== undefined ? { lastRunMessage: result.error } : {}),
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
/** 连续失败 ≥ 3 → 通知(host.log.warn)。 */
|
|
338
|
+
maybeNotify(failures, historyId, nowIso) {
|
|
339
|
+
if (failures >= 3) {
|
|
340
|
+
this.host.log.warn(`自动同步连续失败 ${failures} 次,请检查仓库配置/凭据`);
|
|
341
|
+
// 记录 notifiedAt(更新历史 entry)
|
|
342
|
+
void this.readHistoryFn().then(async (hist) => {
|
|
343
|
+
const entry = hist.autosyncEntries.find((e) => e.createdAt === nowIso);
|
|
344
|
+
if (entry) {
|
|
345
|
+
entry.notifiedAt = nowIso;
|
|
346
|
+
await this.writeConfig({
|
|
347
|
+
...(await this.readConfig()),
|
|
348
|
+
lastRunMessage: `连续失败 ${failures} 次,已通知`,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}).catch(() => { });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/** 从 MergePlan 构造 SyncApplyPlan(autoApply = 所有非 skip 非 conflict 项)。 */
|
|
356
|
+
export function buildAutoApplyPlan(plan) {
|
|
357
|
+
const autoApply = [];
|
|
358
|
+
const review = [];
|
|
359
|
+
const skipped = [];
|
|
360
|
+
for (const s of plan.sections) {
|
|
361
|
+
if (s.decision === 'skip') {
|
|
362
|
+
skipped.push(s);
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (s.decision === 'conflict') {
|
|
366
|
+
review.push(s);
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
// useRemote / keepLocal:都有 merged 数据
|
|
370
|
+
if (s.merged !== undefined) {
|
|
371
|
+
autoApply.push(s);
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
skipped.push(s);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return { autoApply, review, skipped };
|
|
378
|
+
}
|
|
379
|
+
//# sourceMappingURL=autosync-scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autosync-scheduler.js","sourceRoot":"","sources":["../../src/sync/autosync-scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* m-sync-flow:三方合并引擎(纯函数)。
|
|
3
|
+
*
|
|
4
|
+
* 输入:local / remote / ancestor 各分区的 SectionData + 分区 hash。
|
|
5
|
+
* 行为(每分区):
|
|
6
|
+
* - localHash == remoteHash → skip(无变化)
|
|
7
|
+
* - localHash != remoteHash:
|
|
8
|
+
* - localHash == ancestorHash → useRemote(远端独有改动)
|
|
9
|
+
* - remoteHash == ancestorHash → keepLocal(本地独有改动)
|
|
10
|
+
* - 其余 → 进入精细合并:JSON 分区按 top-level key 合并、FilesSection 按 relativePath 合并;
|
|
11
|
+
* 仅双侧均改且值不同的键/文件留为 conflict,其余非重叠变更自动合入。
|
|
12
|
+
*
|
|
13
|
+
* 输出:MergePlan(纯数据,不含副作用),由上层(SyncEngine.merge)返回给 UI 决策。
|
|
14
|
+
*
|
|
15
|
+
* 安全约束:ancestor 为 undefined 时退化为两方合并:local != remote 视为整分区 conflict(拒绝猜测共同祖先)。
|
|
16
|
+
*/
|
|
17
|
+
import type { SectionData, SectionId } from '../schema/types.ts';
|
|
18
|
+
export type MergeDecision = 'useRemote' | 'keepLocal' | 'skip';
|
|
19
|
+
/** 分区内精细合并产生的单条冲突(JSON key 或 FilesSection 单文件)。 */
|
|
20
|
+
export interface MergeConflict {
|
|
21
|
+
/** 冲突位置:JSON 分区是 top-level key;FilesSection 是 relativePath。 */
|
|
22
|
+
path: string;
|
|
23
|
+
kind: 'key' | 'file';
|
|
24
|
+
local?: unknown;
|
|
25
|
+
remote?: unknown;
|
|
26
|
+
ancestor?: unknown;
|
|
27
|
+
}
|
|
28
|
+
export interface MergeSectionResult {
|
|
29
|
+
id: SectionId;
|
|
30
|
+
/** 分区级决策;'skip' 时无 merged/conflicts;'conflict' 时 merged 为 null(仅 conflicts)。 */
|
|
31
|
+
decision: MergeDecision | 'conflict';
|
|
32
|
+
/** 当 decision 为 'useRemote' / 'keepLocal' 时:合并后的整分区数据(= 胜出方原值)。 */
|
|
33
|
+
merged?: SectionData;
|
|
34
|
+
/** 仅 decision === 'conflict' 时非空:分区级冲突或精细合并后剩余冲突。 */
|
|
35
|
+
conflicts: MergeConflict[];
|
|
36
|
+
}
|
|
37
|
+
export interface MergePlan {
|
|
38
|
+
/** 每分区的合并结果;仅含 local/remote/ancestor 三者任一出现的分区。 */
|
|
39
|
+
sections: MergeSectionResult[];
|
|
40
|
+
}
|
|
41
|
+
/** 主入口:三方合并。纯函数,无副作用。 */
|
|
42
|
+
export declare function merge(local: Partial<Record<SectionId, SectionData>>, remote: Partial<Record<SectionId, SectionData>>, ancestor?: Partial<Record<SectionId, SectionData>>): MergePlan;
|