dsh-recall-plugin 2.3.0 → 2.3.2
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 +45 -0
- package/README.en.md +4 -4
- package/README.md +14 -18
- package/lib/client.js +641 -484
- package/lib/config.js +53 -96
- package/lib/diagnostics.js +27 -52
- package/lib/dump-parse.js +79 -61
- package/lib/errors.js +41 -55
- package/lib/index.js +345 -446
- package/lib/maintenance.js +204 -275
- package/lib/routes-core.js +89 -140
- package/lib/routes-manage.js +571 -624
- package/lib/scripts.posix.js +452 -638
- package/lib/scripts.pwsh.js +537 -778
- package/lib/session-info.js +42 -57
- package/lib/snapshots.js +400 -588
- package/lib/store.js +364 -563
- package/package.json +8 -3
package/lib/config.js
CHANGED
|
@@ -1,96 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
gcSnaps:
|
|
20
|
-
gcHours:
|
|
21
|
-
maxFileBytes:
|
|
22
|
-
maxSnapshotsPerWorkspace:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
snapshotEnabled
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
function pickNumber(value, fallback, min) {
|
|
56
|
-
const n = typeof value === 'number' ? value : parseInt(String(value == null ? '' : value), 10)
|
|
57
|
-
if (!Number.isFinite(n) || n < min) return fallback
|
|
58
|
-
return n
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// 环境变量优先(向后兼容),其次 config,最后默认值
|
|
62
|
-
const gcSnaps = pickNumber(process.env.DSH_RECALL_GC_SNAPS, pickNumber(cfg.gcSnaps, 50, 1), 1)
|
|
63
|
-
const gcHours = pickNumber(process.env.DSH_RECALL_GC_HOURS, pickNumber(cfg.gcHours, 24, 1), 1)
|
|
64
|
-
const maxFileBytes = pickNumber(cfg.maxFileBytes, 104857600, 1024)
|
|
65
|
-
// 每工作区快照上限:0 或负值语义 = 不限制(给想全保留的用户出口);
|
|
66
|
-
// 非数值回退默认 500。默认 500 ≈ 重度使用一周量级,太小会静默丢历史
|
|
67
|
-
// 撤回点,太大失去防膨胀意义。
|
|
68
|
-
const rawMax = typeof cfg.maxSnapshotsPerWorkspace === 'number'
|
|
69
|
-
? cfg.maxSnapshotsPerWorkspace
|
|
70
|
-
: parseInt(String(cfg.maxSnapshotsPerWorkspace == null ? '' : cfg.maxSnapshotsPerWorkspace), 10)
|
|
71
|
-
const maxSnapshotsPerWorkspace = Number.isFinite(rawMax) ? Math.max(0, rawMax) : 500
|
|
72
|
-
|
|
73
|
-
const baseExcludes = Array.isArray(cfg.baseExcludes) && cfg.baseExcludes.length
|
|
74
|
-
? cfg.baseExcludes.filter((p) => typeof p === 'string' && p.trim())
|
|
75
|
-
: BASE_EXCLUDES
|
|
76
|
-
|
|
77
|
-
const refillDraft = typeof cfg.refillDraft === 'boolean' ? cfg.refillDraft : true
|
|
78
|
-
|
|
79
|
-
// 快照总开关:false 冻结「新建」(session/event 短路,见 index.js),
|
|
80
|
-
// 已有快照的撤回链路不受影响——关闭只停增量,不销毁存量。
|
|
81
|
-
const snapshotEnabled = typeof cfg.snapshotEnabled === 'boolean' ? cfg.snapshotEnabled : true
|
|
82
|
-
|
|
83
|
-
// 撤回后是否归档原会话:关闭时原会话保留在侧栏(fork 新会话仍打开),
|
|
84
|
-
// 供用户对照回退前后上下文;默认开(归档只是隐藏、可恢复)。
|
|
85
|
-
const archiveOriginal = typeof cfg.archiveOriginal === 'boolean' ? cfg.archiveOriginal : true
|
|
86
|
-
|
|
87
|
-
// 按时间保留(S2-3):0 或负值 = 不启用(静默删历史撤回点必须显式
|
|
88
|
-
// opt-in);非数值回退 0。与 maxSnapshotsPerWorkspace(条数维度)并存,
|
|
89
|
-
// 各自独立触发——见 maintenance.enforceRetention。
|
|
90
|
-
const rawDays = typeof cfg.retentionDays === 'number'
|
|
91
|
-
? cfg.retentionDays
|
|
92
|
-
: parseInt(String(cfg.retentionDays == null ? '' : cfg.retentionDays), 10)
|
|
93
|
-
const retentionDays = Number.isFinite(rawDays) ? Math.max(0, rawDays) : 0
|
|
94
|
-
|
|
95
|
-
return { gcSnaps, gcHours, maxFileBytes, maxSnapshotsPerWorkspace, baseExcludes, refillDraft, snapshotEnabled, archiveOriginal, retentionDays }
|
|
96
|
-
}
|
|
1
|
+
import Schema from "@deepseek-ai/schemastery";
|
|
2
|
+
const Config = Schema.object({
|
|
3
|
+
gcSnaps: Schema.number().default(50).description("\u6BCF\u79EF\u7D2F\u591A\u5C11\u6761\u5FEB\u7167\u89E6\u53D1\u4E00\u6B21 git gc"),
|
|
4
|
+
gcHours: Schema.number().default(24).description("\u8DDD\u4E0A\u6B21 gc \u8D85\u8FC7\u591A\u5C11\u5C0F\u65F6\u89E6\u53D1\uFF08\u4E0E\u6761\u6570\u5148\u5230\u5148\u89E6\u53D1\uFF09"),
|
|
5
|
+
maxFileBytes: Schema.number().default(104857600).description("\u8D85\u8FC7\u8BE5\u5B57\u8282\u6570\u7684\u6587\u4EF6\u4E0D\u8FDB\u5FEB\u7167\u3001\u4E0D\u88AB\u56DE\u9000\u89E6\u78B0"),
|
|
6
|
+
maxSnapshotsPerWorkspace: Schema.number().default(500).description("\u6BCF\u4E2A\u5DE5\u4F5C\u533A\u4FDD\u7559\u7684\u6700\u5927\u5FEB\u7167\u6570\uFF0C\u8D85\u9650\u5220\u9664\u6700\u65E7\u7684"),
|
|
7
|
+
// 排除表必须同时覆盖两种存储目录名:降级存储是项目内 .dsh-recall-snapshots/,
|
|
8
|
+
// 而 home 存储目录名是 dsh-recall-snapshots/(无点)——工作区 root 恰为
|
|
9
|
+
// HOME 时(容器 root=/root 等)它落在工作区内,漏排除会让 git add -A
|
|
10
|
+
// 把影子仓库自己吞进去、快照全部失败(issue #6)
|
|
11
|
+
baseExcludes: Schema.array(Schema.string()).default([".git", "node_modules/", ".dsh-recall-snapshots/", "dsh-recall-snapshots/"]).description("\u57FA\u7840\u6392\u9664\u8868\uFF08gitignore \u8BED\u6CD5\uFF0C\u4F18\u5148\u7EA7\u4F4E\u4E8E exclude.txt\uFF09"),
|
|
12
|
+
refillDraft: Schema.boolean().default(true).description("\u64A4\u56DE\u540E\u628A\u88AB\u64A4\u56DE\u7684\u6D88\u606F\u6587\u672C\u56DE\u586B\u5230\u8F93\u5165\u6846"),
|
|
13
|
+
snapshotEnabled: Schema.boolean().default(true).description("\u542F\u7528\u6D88\u606F\u5FEB\u7167\uFF08\u5173\u95ED\u540E\u4E0D\u518D\u65B0\u5EFA\uFF0C\u5DF2\u6709\u5FEB\u7167\u4ECD\u53EF\u64A4\u56DE\uFF09"),
|
|
14
|
+
archiveOriginal: Schema.boolean().default(true).description("\u64A4\u56DE\u540E\u5F52\u6863\u539F\u4F1A\u8BDD\uFF08\u5173\u95ED\u540E\u539F\u4F1A\u8BDD\u4FDD\u7559\u5728\u5217\u8868\u4E2D\uFF09"),
|
|
15
|
+
retentionDays: Schema.number().default(0).description("\u6309\u5929\u6570\u4FDD\u7559\u5FEB\u7167\uFF0C\u8D85\u671F\u81EA\u52A8\u5220\u9664\uFF1B0 \u8868\u793A\u4E0D\u542F\u7528")
|
|
16
|
+
});
|
|
17
|
+
const BASE_EXCLUDES = [".git", "node_modules/", ".dsh-recall-snapshots/", "dsh-recall-snapshots/"];
|
|
18
|
+
const DEFAULTS = {
|
|
19
|
+
gcSnaps: 50,
|
|
20
|
+
gcHours: 24,
|
|
21
|
+
maxFileBytes: 104857600,
|
|
22
|
+
maxSnapshotsPerWorkspace: 500,
|
|
23
|
+
baseExcludes: BASE_EXCLUDES,
|
|
24
|
+
refillDraft: true,
|
|
25
|
+
snapshotEnabled: true,
|
|
26
|
+
archiveOriginal: true,
|
|
27
|
+
retentionDays: 0
|
|
28
|
+
};
|
|
29
|
+
function createConfig(raw) {
|
|
30
|
+
const cfg = raw && typeof raw === "object" ? raw : {};
|
|
31
|
+
function pickNumber(value, fallback, min) {
|
|
32
|
+
const n = typeof value === "number" ? value : parseInt(String(value == null ? "" : value), 10);
|
|
33
|
+
if (!Number.isFinite(n) || n < min) return fallback;
|
|
34
|
+
return n;
|
|
35
|
+
}
|
|
36
|
+
const gcSnaps = pickNumber(process.env.DSH_RECALL_GC_SNAPS, pickNumber(cfg.gcSnaps, 50, 1), 1);
|
|
37
|
+
const gcHours = pickNumber(process.env.DSH_RECALL_GC_HOURS, pickNumber(cfg.gcHours, 24, 1), 1);
|
|
38
|
+
const maxFileBytes = pickNumber(cfg.maxFileBytes, 104857600, 1024);
|
|
39
|
+
const rawMax = typeof cfg.maxSnapshotsPerWorkspace === "number" ? cfg.maxSnapshotsPerWorkspace : parseInt(String(cfg.maxSnapshotsPerWorkspace == null ? "" : cfg.maxSnapshotsPerWorkspace), 10);
|
|
40
|
+
const maxSnapshotsPerWorkspace = Number.isFinite(rawMax) ? Math.max(0, rawMax) : 500;
|
|
41
|
+
const baseExcludes = Array.isArray(cfg.baseExcludes) && cfg.baseExcludes.length ? cfg.baseExcludes.filter((p) => typeof p === "string" && p.trim()) : BASE_EXCLUDES;
|
|
42
|
+
const refillDraft = typeof cfg.refillDraft === "boolean" ? cfg.refillDraft : true;
|
|
43
|
+
const snapshotEnabled = typeof cfg.snapshotEnabled === "boolean" ? cfg.snapshotEnabled : true;
|
|
44
|
+
const archiveOriginal = typeof cfg.archiveOriginal === "boolean" ? cfg.archiveOriginal : true;
|
|
45
|
+
const rawDays = typeof cfg.retentionDays === "number" ? cfg.retentionDays : parseInt(String(cfg.retentionDays == null ? "" : cfg.retentionDays), 10);
|
|
46
|
+
const retentionDays = Number.isFinite(rawDays) ? Math.max(0, rawDays) : 0;
|
|
47
|
+
return { gcSnaps, gcHours, maxFileBytes, maxSnapshotsPerWorkspace, baseExcludes, refillDraft, snapshotEnabled, archiveOriginal, retentionDays };
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
Config,
|
|
51
|
+
DEFAULTS,
|
|
52
|
+
createConfig
|
|
53
|
+
};
|
package/lib/diagnostics.js
CHANGED
|
@@ -1,59 +1,34 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-recall-plugin — 环境错误诊断(纯函数模块,无 ctx 依赖)
|
|
3
|
-
*
|
|
4
|
-
* 环境类失败(锁冲突/磁盘满/权限等)的「识别 → 可行动提示」分层,仿
|
|
5
|
-
* errors.js 的机器码与人文案分层:kind 供机器分流(recordError 富集、
|
|
6
|
-
* status API、未来设置页过滤),提示文本在 Host 侧生成——client 的 toast
|
|
7
|
-
* 运行时读 res.error('快照失败:' + String(res.error).slice(0, 140)),
|
|
8
|
-
* Host 换 error 字段文本即生效,client 零改动。
|
|
9
|
-
*
|
|
10
|
-
* 文案硬约束( motivated by issue #11:锁路径就 100+ 字符,嵌进提示必被
|
|
11
|
-
* 140 截断出残句):提示不嵌原始路径,目标 ≤120 字符、硬上限 140;完整
|
|
12
|
-
* 原文(含路径)由设置页「最近错误」承载。
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
// 分类模式表,按根因优先级排列(同一文本命中多类时先命中者胜——
|
|
16
|
-
// 如 `Unable to create '…lock': No space left on device` 同时命中 lock 与
|
|
17
|
-
// space,磁盘满是根因,space 必须排在 lock 前面)。模式为不区分大小写的
|
|
18
|
-
// 正则,覆盖 git 两平台措辞(POSIX `command not found` / win32 `not
|
|
19
|
-
// recognized`)与常见 errno 文本。
|
|
20
1
|
const ENV_PATTERNS = [
|
|
21
|
-
[
|
|
22
|
-
[
|
|
23
|
-
[
|
|
24
|
-
[
|
|
25
|
-
[
|
|
26
|
-
]
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// 环境错误分类:命中返回 kind,未命中返回 null(未识别错误保现状回落
|
|
39
|
-
// 原文,误判只影响 toast 文案不影响功能)。git > space > permission >
|
|
40
|
-
// lock > mkdir 的表序即根因优先级,勿按字母序重排。
|
|
41
|
-
export function classifyEnvError(text) {
|
|
42
|
-
const s = String(text || '')
|
|
2
|
+
["git", [/command not found/i, /not recognized/i, /git: not found/i, /is not a git command/i]],
|
|
3
|
+
["space", [/no space left on device/i, /disk quota exceeded/i, /enospc/i]],
|
|
4
|
+
["permission", [/permission denied/i, /operation not permitted/i, /not permitted/i, /access is denied/i]],
|
|
5
|
+
["lock", [/could not lock .*file exists/i, /unable to create .*\.lock/i, /fatal: cannot lock/i]],
|
|
6
|
+
["mkdir", [/fatal: cannot mkdir .*file exists/i, /mkdir: cannot create directory/i]]
|
|
7
|
+
];
|
|
8
|
+
const ENV_HINTS = {
|
|
9
|
+
git: "\u672A\u68C0\u6D4B\u5230 git CLI \u6216\u7248\u672C\u8FC7\u65E7\uFF1A\u8BF7\u5B89\u88C5\u6216\u5347\u7EA7 git\uFF0C\u5B8C\u6210\u540E\u81EA\u52A8\u6062\u590D",
|
|
10
|
+
space: "\u78C1\u76D8\u7A7A\u95F4\u5DF2\u6EE1\uFF0C\u5FEB\u7167\u5199\u5165\u5931\u8D25\uFF1A\u6E05\u7406\u78C1\u76D8\u7A7A\u95F4\u540E\u81EA\u52A8\u6062\u590D",
|
|
11
|
+
permission: "\u5FEB\u7167\u76EE\u5F55\u65E0\u5199\u5165\u6743\u9650\uFF1A\u8BF7\u68C0\u67E5\u76EE\u5F55\u6743\u9650\u540E\u91CD\u8BD5",
|
|
12
|
+
lock: "\u7591\u4F3C\u591A\u4E2A DSH \u5B9E\u4F8B\u5E76\u53D1\u4F7F\u7528\u540C\u4E00\u5FEB\u7167\u5E93\uFF1A\u8BF7\u786E\u8BA4\u53EA\u542F\u52A8\u4E86\u4E00\u4E2A\uFF1B\u786E\u8BA4\u540E\u4ECD\u5931\u8D25\u65F6\uFF0C\u6309\u300C\u8BBE\u7F6E \xB7 \u63D2\u4EF6\u914D\u7F6E \xB7 \u6700\u8FD1\u9519\u8BEF\u300D\u4E2D\u7684\u8DEF\u5F84\u5220\u9664\u9501\u6587\u4EF6",
|
|
13
|
+
mkdir: "\u5FEB\u7167\u5B58\u50A8\u76EE\u5F55\u88AB\u540C\u540D\u6587\u4EF6\u5360\u7528\uFF1A\u5904\u7406\u540E\u81EA\u52A8\u6062\u590D"
|
|
14
|
+
};
|
|
15
|
+
function classifyEnvError(text) {
|
|
16
|
+
const s = String(text || "");
|
|
43
17
|
for (const [kind, patterns] of ENV_PATTERNS) {
|
|
44
18
|
for (const p of patterns) {
|
|
45
|
-
if (p.test(s)) return kind
|
|
19
|
+
if (p.test(s)) return kind;
|
|
46
20
|
}
|
|
47
21
|
}
|
|
48
|
-
return null
|
|
22
|
+
return null;
|
|
49
23
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const text = String(raw || '')
|
|
56
|
-
const kind = classifyEnvError(text)
|
|
57
|
-
if (!kind) return { error: text.slice(0, 300), kind: 'unknown' }
|
|
58
|
-
return { error: ENV_HINTS[kind], kind }
|
|
24
|
+
function buildFeedbackError(raw) {
|
|
25
|
+
const text = String(raw || "");
|
|
26
|
+
const kind = classifyEnvError(text);
|
|
27
|
+
if (!kind) return { error: text.slice(0, 300), kind: "unknown" };
|
|
28
|
+
return { error: ENV_HINTS[kind], kind };
|
|
59
29
|
}
|
|
30
|
+
export {
|
|
31
|
+
ENV_HINTS,
|
|
32
|
+
buildFeedbackError,
|
|
33
|
+
classifyEnvError
|
|
34
|
+
};
|
package/lib/dump-parse.js
CHANGED
|
@@ -1,78 +1,96 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* re-export 保持既有 import 路径稳定。
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// 解析 storesDumpScript 的定界输出:dir → { root, entries, lineage }。逐行
|
|
12
|
-
// 状态机(==DIR / ROOT / INDEXBEGIN..INDEXEND / LINEAGEBEGIN..LINEAGEEND),
|
|
13
|
-
// 单个 store 的 JSON 损坏只丢它自己。
|
|
14
|
-
// PF-4:LINEAGE 段承载 lineage.json 原文(与 INDEX 段同构)——manage lineage
|
|
15
|
-
// 原实现对每个 root 串行 loadLineage(每 root 一条进程,20 工作区 ≈ 10s),
|
|
16
|
-
// 并入 dump 后零新增进程。无 LINEAGE 段(脚本/Host 版本错位的理论场景)按
|
|
17
|
-
// 无 lineage 处理,解析容错;lineage.json 损坏按空处理(与 loadLineage 的
|
|
18
|
-
// 既有语义一致:损坏不致命,树退化为普通分组)。
|
|
19
|
-
export function parseStoresDump(text) {
|
|
20
|
-
const map = new Map()
|
|
21
|
-
let cur = null
|
|
22
|
-
let inIndex = false
|
|
23
|
-
let indexLines = []
|
|
24
|
-
let inLineage = false
|
|
25
|
-
let lineageLines = []
|
|
1
|
+
function parseStoresDump(text) {
|
|
2
|
+
const map = /* @__PURE__ */ new Map();
|
|
3
|
+
let cur = null;
|
|
4
|
+
let inIndex = false;
|
|
5
|
+
let indexLines = [];
|
|
6
|
+
let inLineage = false;
|
|
7
|
+
let lineageLines = [];
|
|
26
8
|
function flush() {
|
|
27
|
-
if (!cur) return
|
|
28
|
-
const raw = indexLines.join(
|
|
9
|
+
if (!cur) return;
|
|
10
|
+
const raw = indexLines.join("\n").trim();
|
|
29
11
|
if (raw) {
|
|
30
12
|
try {
|
|
31
|
-
const arr = JSON.parse(raw)
|
|
32
|
-
if (Array.isArray(arr)) cur.entries = arr
|
|
33
|
-
} catch (error) {
|
|
13
|
+
const arr = JSON.parse(raw);
|
|
14
|
+
if (Array.isArray(arr)) cur.entries = arr;
|
|
15
|
+
} catch (error) {
|
|
16
|
+
}
|
|
34
17
|
}
|
|
35
|
-
const lraw = lineageLines.join(
|
|
18
|
+
const lraw = lineageLines.join("\n").trim();
|
|
36
19
|
if (lraw) {
|
|
37
20
|
try {
|
|
38
|
-
const larr = JSON.parse(lraw)
|
|
21
|
+
const larr = JSON.parse(lraw);
|
|
39
22
|
if (Array.isArray(larr)) {
|
|
40
|
-
cur.lineage = larr.filter((e) => e && typeof e.childId ===
|
|
23
|
+
cur.lineage = larr.filter((e) => e && typeof e.childId === "string" && typeof e.parentId === "string");
|
|
41
24
|
}
|
|
42
|
-
} catch (error) {
|
|
25
|
+
} catch (error) {
|
|
26
|
+
}
|
|
43
27
|
}
|
|
44
|
-
map.set(cur.dir, cur)
|
|
45
|
-
cur = null
|
|
28
|
+
map.set(cur.dir, cur);
|
|
29
|
+
cur = null;
|
|
46
30
|
}
|
|
47
31
|
for (const line of String(text).split(/\r?\n/)) {
|
|
48
|
-
if (line.indexOf(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
32
|
+
if (line.indexOf("==DIR ") === 0) {
|
|
33
|
+
flush();
|
|
34
|
+
cur = { dir: line.slice(6).trim(), root: null, entries: null, lineage: null };
|
|
35
|
+
inIndex = false;
|
|
36
|
+
inLineage = false;
|
|
37
|
+
indexLines = [];
|
|
38
|
+
lineageLines = [];
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (!cur) continue;
|
|
42
|
+
if (line.indexOf("ROOT ") === 0) {
|
|
43
|
+
const v = line.slice(5).trim();
|
|
44
|
+
cur.root = v || null;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (line === "INDEXBEGIN") {
|
|
48
|
+
inIndex = true;
|
|
49
|
+
indexLines = [];
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (line === "INDEXEND") {
|
|
53
|
+
inIndex = false;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (line === "LINEAGEBEGIN") {
|
|
57
|
+
inLineage = true;
|
|
58
|
+
lineageLines = [];
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (line === "LINEAGEEND") {
|
|
62
|
+
inLineage = false;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (inIndex) indexLines.push(line);
|
|
66
|
+
else if (inLineage) lineageLines.push(line);
|
|
57
67
|
}
|
|
58
|
-
flush()
|
|
59
|
-
return map
|
|
68
|
+
flush();
|
|
69
|
+
return map;
|
|
60
70
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (line
|
|
71
|
-
|
|
71
|
+
function parseExcludeDump(text) {
|
|
72
|
+
const map = /* @__PURE__ */ new Map();
|
|
73
|
+
let cur = null;
|
|
74
|
+
for (const line of String(text || "").split(/\r?\n/)) {
|
|
75
|
+
if (line.indexOf("EXCLBEGIN ") === 0) {
|
|
76
|
+
cur = line.slice("EXCLBEGIN ".length).trim();
|
|
77
|
+
map.set(cur, "");
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (line === "EXCLEND") {
|
|
81
|
+
cur = null;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
72
84
|
if (cur !== null && line) {
|
|
73
|
-
|
|
74
|
-
|
|
85
|
+
try {
|
|
86
|
+
map.set(cur, Buffer.from(line, "base64").toString("utf8"));
|
|
87
|
+
} catch (error) {
|
|
88
|
+
}
|
|
75
89
|
}
|
|
76
90
|
}
|
|
77
|
-
return map
|
|
91
|
+
return map;
|
|
78
92
|
}
|
|
93
|
+
export {
|
|
94
|
+
parseExcludeDump,
|
|
95
|
+
parseStoresDump
|
|
96
|
+
};
|
package/lib/errors.js
CHANGED
|
@@ -1,57 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// 回退失败(H1)——可能已自动救援恢复;message 携带救援结果
|
|
21
|
-
export const RECALL_ROLLBACK_FAILED = 'ROLLBACK_FAILED'
|
|
22
|
-
// 排除配置写入路径不在已知白名单内(防「借 API 写任意文件」)
|
|
23
|
-
export const RECALL_UNKNOWN_PATH = 'UNKNOWN_PATH'
|
|
24
|
-
// 配置字段类型/取值非法
|
|
25
|
-
export const RECALL_BAD_TYPE = 'BAD_TYPE'
|
|
26
|
-
// 配置补丁为空(无可写字段)
|
|
27
|
-
export const RECALL_EMPTY_PATCH = 'EMPTY_PATCH'
|
|
28
|
-
// settings 服务未组装(非 web 部署 / 未挂载)
|
|
29
|
-
export const RECALL_SETTINGS_UNAVAILABLE = 'SETTINGS_UNAVAILABLE'
|
|
30
|
-
// settings.update 写入被拒
|
|
31
|
-
export const RECALL_SETTINGS_WRITE_FAILED = 'SETTINGS_WRITE_FAILED'
|
|
32
|
-
// 请求体超过 1MB 上限(errBody 统一映射)
|
|
33
|
-
export const RECALL_BODY_TOO_LARGE = 'BODY_TOO_LARGE'
|
|
34
|
-
// 系统异常/未分类错误兜底(errBody 统一映射)
|
|
35
|
-
export const RECALL_ERROR = 'ERROR'
|
|
36
|
-
// 无法解析当前工作区(manage usage/delete)
|
|
37
|
-
export const RECALL_NO_ROOT = 'NO_ROOT'
|
|
38
|
-
// 缺少会话 ID(manage delete scope=session)
|
|
39
|
-
export const RECALL_NO_SESSION = 'NO_SESSION'
|
|
40
|
-
// 管理操作部分完成(deleteAll 有 store 失败)
|
|
41
|
-
export const RECALL_PARTIAL_DELETE = 'PARTIAL_DELETE'
|
|
42
|
-
// 未知管理操作(manage 端点 op 未识别)
|
|
43
|
-
export const RECALL_UNKNOWN_OP = 'UNKNOWN_OP'
|
|
44
|
-
// 未知 API 端点(webServer 路由 404)
|
|
45
|
-
export const RECALL_UNKNOWN_ENDPOINT = 'UNKNOWN_ENDPOINT'
|
|
46
|
-
|
|
47
|
-
// 语义锚点:H2 的索引损坏经 status 端点 errors 通道暴露(recordError 文本
|
|
48
|
-
// 前缀 'recall index corrupt'),不作为端点 code 返回。保留单一命名供
|
|
49
|
-
// 一致性扫描与未来若要升级为端点 code 时复用,避免「损坏」这一事实在
|
|
50
|
-
// 代码里无归属。
|
|
51
|
-
export const RECALL_INDEX_CORRUPT = 'INDEX_CORRUPT'
|
|
52
|
-
|
|
53
|
-
// 全量常量集合:供单测做「端点返回的 code 都在表内」的一致性扫描
|
|
54
|
-
export const ALL_CODES = Object.freeze([
|
|
1
|
+
const RECALL_STALE = "STALE";
|
|
2
|
+
const RECALL_NO_SNAPSHOT = "NO_SNAPSHOT";
|
|
3
|
+
const RECALL_NO_STORE = "NO_STORE";
|
|
4
|
+
const RECALL_AGENT_BUSY = "AGENT_BUSY";
|
|
5
|
+
const RECALL_ROLLBACK_FAILED = "ROLLBACK_FAILED";
|
|
6
|
+
const RECALL_UNKNOWN_PATH = "UNKNOWN_PATH";
|
|
7
|
+
const RECALL_BAD_TYPE = "BAD_TYPE";
|
|
8
|
+
const RECALL_EMPTY_PATCH = "EMPTY_PATCH";
|
|
9
|
+
const RECALL_SETTINGS_UNAVAILABLE = "SETTINGS_UNAVAILABLE";
|
|
10
|
+
const RECALL_SETTINGS_WRITE_FAILED = "SETTINGS_WRITE_FAILED";
|
|
11
|
+
const RECALL_BODY_TOO_LARGE = "BODY_TOO_LARGE";
|
|
12
|
+
const RECALL_ERROR = "ERROR";
|
|
13
|
+
const RECALL_NO_ROOT = "NO_ROOT";
|
|
14
|
+
const RECALL_NO_SESSION = "NO_SESSION";
|
|
15
|
+
const RECALL_PARTIAL_DELETE = "PARTIAL_DELETE";
|
|
16
|
+
const RECALL_UNKNOWN_OP = "UNKNOWN_OP";
|
|
17
|
+
const RECALL_UNKNOWN_ENDPOINT = "UNKNOWN_ENDPOINT";
|
|
18
|
+
const RECALL_INDEX_CORRUPT = "INDEX_CORRUPT";
|
|
19
|
+
const ALL_CODES = Object.freeze([
|
|
55
20
|
RECALL_STALE,
|
|
56
21
|
RECALL_NO_SNAPSHOT,
|
|
57
22
|
RECALL_NO_STORE,
|
|
@@ -69,5 +34,26 @@ export const ALL_CODES = Object.freeze([
|
|
|
69
34
|
RECALL_PARTIAL_DELETE,
|
|
70
35
|
RECALL_UNKNOWN_OP,
|
|
71
36
|
RECALL_UNKNOWN_ENDPOINT,
|
|
37
|
+
RECALL_INDEX_CORRUPT
|
|
38
|
+
]);
|
|
39
|
+
export {
|
|
40
|
+
ALL_CODES,
|
|
41
|
+
RECALL_AGENT_BUSY,
|
|
42
|
+
RECALL_BAD_TYPE,
|
|
43
|
+
RECALL_BODY_TOO_LARGE,
|
|
44
|
+
RECALL_EMPTY_PATCH,
|
|
45
|
+
RECALL_ERROR,
|
|
72
46
|
RECALL_INDEX_CORRUPT,
|
|
73
|
-
|
|
47
|
+
RECALL_NO_ROOT,
|
|
48
|
+
RECALL_NO_SESSION,
|
|
49
|
+
RECALL_NO_SNAPSHOT,
|
|
50
|
+
RECALL_NO_STORE,
|
|
51
|
+
RECALL_PARTIAL_DELETE,
|
|
52
|
+
RECALL_ROLLBACK_FAILED,
|
|
53
|
+
RECALL_SETTINGS_UNAVAILABLE,
|
|
54
|
+
RECALL_SETTINGS_WRITE_FAILED,
|
|
55
|
+
RECALL_STALE,
|
|
56
|
+
RECALL_UNKNOWN_ENDPOINT,
|
|
57
|
+
RECALL_UNKNOWN_OP,
|
|
58
|
+
RECALL_UNKNOWN_PATH
|
|
59
|
+
};
|