tanmi-dock 1.0.3 → 1.0.4
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/README.md +18 -0
- package/dist/commands/calibrate.d.ts +10 -0
- package/dist/commands/calibrate.d.ts.map +1 -0
- package/dist/commands/calibrate.js +158 -0
- package/dist/commands/calibrate.js.map +1 -0
- package/dist/commands/check.d.ts +9 -1
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +261 -94
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/clean.d.ts +2 -6
- package/dist/commands/clean.d.ts.map +1 -1
- package/dist/commands/clean.js +224 -209
- package/dist/commands/clean.js.map +1 -1
- package/dist/commands/dashboard.d.ts.map +1 -1
- package/dist/commands/dashboard.js +56 -28
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/link.d.ts +6 -6
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/link.js +417 -196
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/migrate.d.ts +11 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +277 -57
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/reset.d.ts.map +1 -1
- package/dist/commands/reset.js +19 -24
- package/dist/commands/reset.js.map +1 -1
- package/dist/commands/status.d.ts +4 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +37 -20
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/unavailable.js.map +1 -1
- package/dist/commands/unlink.d.ts.map +1 -1
- package/dist/commands/unlink.js +43 -39
- package/dist/commands/unlink.js.map +1 -1
- package/dist/core/config.d.ts +3 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +6 -3
- package/dist/core/config.js.map +1 -1
- package/dist/core/parser.js +1 -1
- package/dist/core/parser.js.map +1 -1
- package/dist/core/registry.d.ts +11 -29
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/registry.js +46 -113
- package/dist/core/registry.js.map +1 -1
- package/dist/core/space/calibration-executor.d.ts +16 -0
- package/dist/core/space/calibration-executor.d.ts.map +1 -0
- package/dist/core/space/calibration-executor.js +142 -0
- package/dist/core/space/calibration-executor.js.map +1 -0
- package/dist/core/space/calibration-planner.d.ts +5 -0
- package/dist/core/space/calibration-planner.d.ts.map +1 -0
- package/dist/core/space/calibration-planner.js +157 -0
- package/dist/core/space/calibration-planner.js.map +1 -0
- package/dist/core/space/cleanup-executor.d.ts +15 -0
- package/dist/core/space/cleanup-executor.d.ts.map +1 -0
- package/dist/core/space/cleanup-executor.js +188 -0
- package/dist/core/space/cleanup-executor.js.map +1 -0
- package/dist/core/space/cleanup-journal.d.ts +43 -0
- package/dist/core/space/cleanup-journal.d.ts.map +1 -0
- package/dist/core/space/cleanup-journal.js +96 -0
- package/dist/core/space/cleanup-journal.js.map +1 -0
- package/dist/core/space/cleanup-planner.d.ts +14 -0
- package/dist/core/space/cleanup-planner.d.ts.map +1 -0
- package/dist/core/space/cleanup-planner.js +166 -0
- package/dist/core/space/cleanup-planner.js.map +1 -0
- package/dist/core/space/cleanup-selection.d.ts +10 -0
- package/dist/core/space/cleanup-selection.d.ts.map +1 -0
- package/dist/core/space/cleanup-selection.js +84 -0
- package/dist/core/space/cleanup-selection.js.map +1 -0
- package/dist/core/space/estimator.d.ts +11 -0
- package/dist/core/space/estimator.d.ts.map +1 -0
- package/dist/core/space/estimator.js +142 -0
- package/dist/core/space/estimator.js.map +1 -0
- package/dist/core/space/inventory.d.ts +15 -0
- package/dist/core/space/inventory.d.ts.map +1 -0
- package/dist/core/space/inventory.js +587 -0
- package/dist/core/space/inventory.js.map +1 -0
- package/dist/core/space/meter.d.ts +5 -0
- package/dist/core/space/meter.d.ts.map +1 -0
- package/dist/core/space/meter.js +87 -0
- package/dist/core/space/meter.js.map +1 -0
- package/dist/core/space/migration-journal.d.ts +31 -0
- package/dist/core/space/migration-journal.d.ts.map +1 -0
- package/dist/core/space/migration-journal.js +121 -0
- package/dist/core/space/migration-journal.js.map +1 -0
- package/dist/core/space/service.d.ts +26 -0
- package/dist/core/space/service.d.ts.map +1 -0
- package/dist/core/space/service.js +54 -0
- package/dist/core/space/service.js.map +1 -0
- package/dist/core/space/types.d.ts +241 -0
- package/dist/core/space/types.d.ts.map +1 -0
- package/dist/core/space/types.js +13 -0
- package/dist/core/space/types.js.map +1 -0
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +12 -6
- package/dist/core/store.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/atomic-file.d.ts +6 -0
- package/dist/utils/atomic-file.d.ts.map +1 -0
- package/dist/utils/atomic-file.js +37 -0
- package/dist/utils/atomic-file.js.map +1 -0
- package/dist/utils/fs-utils.d.ts +18 -1
- package/dist/utils/fs-utils.d.ts.map +1 -1
- package/dist/utils/fs-utils.js +150 -2
- package/dist/utils/fs-utils.js.map +1 -1
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.js +2 -2
- package/dist/utils/prompt.d.ts.map +1 -1
- package/dist/utils/prompt.js +4 -4
- package/dist/utils/prompt.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/check.js
CHANGED
|
@@ -9,7 +9,11 @@ import { getDiskInfo, formatSize } from '../utils/disk.js';
|
|
|
9
9
|
import * as config from '../core/config.js';
|
|
10
10
|
import { getRegistry } from '../core/registry.js';
|
|
11
11
|
import * as store from '../core/store.js';
|
|
12
|
-
import { KNOWN_PLATFORM_VALUES } from '../core/platform.js';
|
|
12
|
+
import { KNOWN_PLATFORM_VALUES, SHARED_PLATFORM } from '../core/platform.js';
|
|
13
|
+
import { SpaceService } from '../core/space/service.js';
|
|
14
|
+
import { CleanupJournal } from '../core/space/cleanup-journal.js';
|
|
15
|
+
import { MigrationJournal } from '../core/space/migration-journal.js';
|
|
16
|
+
import { withGlobalLock } from '../utils/global-lock.js';
|
|
13
17
|
import { info, warn, success, error, hint, blank, title, separator, colorize, debug, } from '../utils/logger.js';
|
|
14
18
|
import { selectWithCancel, checkboxWithCancel, confirmWithCancel, PROMPT_CANCELLED, } from '../utils/prompt.js';
|
|
15
19
|
// ============ 命令创建 ============
|
|
@@ -27,7 +31,17 @@ export function createCheckCommand() {
|
|
|
27
31
|
}
|
|
28
32
|
// ============ 主流程 ============
|
|
29
33
|
async function runCheck(options) {
|
|
30
|
-
|
|
34
|
+
if (options.dryRun) {
|
|
35
|
+
const cfg = await config.load({ migrate: false });
|
|
36
|
+
if (cfg && config.checkConfigVersion(cfg) === 'migrate') {
|
|
37
|
+
error('配置需要迁移;dry-run 不会修改配置,请先运行一次普通命令完成迁移');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const result = await collectAllIssues({
|
|
42
|
+
integrity: options.integrity,
|
|
43
|
+
readOnly: options.dryRun,
|
|
44
|
+
});
|
|
31
45
|
// JSON 输出
|
|
32
46
|
if (options.json) {
|
|
33
47
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -35,7 +49,8 @@ async function runCheck(options) {
|
|
|
35
49
|
}
|
|
36
50
|
// 渲染报告
|
|
37
51
|
renderReport(result);
|
|
38
|
-
const
|
|
52
|
+
const hasIntegrityIssues = result.summary.integrityIssues > 0;
|
|
53
|
+
const hasIssues = hasIntegrityIssues || result.summary.spaceIssues > 0;
|
|
39
54
|
const hasEnvErrors = result.summary.envErrors > 0;
|
|
40
55
|
// dry-run 模式
|
|
41
56
|
if (options.dryRun) {
|
|
@@ -55,6 +70,10 @@ async function runCheck(options) {
|
|
|
55
70
|
if (!hasIssues) {
|
|
56
71
|
return;
|
|
57
72
|
}
|
|
73
|
+
if (!hasIntegrityIssues) {
|
|
74
|
+
hint('空间差异需要通过详细检查确认,本次没有自动修改注册记录');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
58
77
|
// --fix 模式:直接修复
|
|
59
78
|
if (options.fix) {
|
|
60
79
|
await fixAllIssues(result.integrity, { force: options.force });
|
|
@@ -65,8 +84,58 @@ async function runCheck(options) {
|
|
|
65
84
|
}
|
|
66
85
|
// ============ 数据收集 ============
|
|
67
86
|
async function collectAllIssues(options) {
|
|
68
|
-
const environment = await checkEnvironment();
|
|
87
|
+
const environment = await checkEnvironment(options?.readOnly);
|
|
69
88
|
const integrity = await checkIntegrity(options);
|
|
89
|
+
const space = { issues: [] };
|
|
90
|
+
try {
|
|
91
|
+
const registry = getRegistry();
|
|
92
|
+
await registry.load({ migrate: !options?.readOnly });
|
|
93
|
+
const storePath = await store.getStorePath();
|
|
94
|
+
debug(`[space] audit 开始: store=${storePath}, source=td-check`);
|
|
95
|
+
const snapshot = await new SpaceService(registry, storePath).buildSnapshot('audit');
|
|
96
|
+
space.snapshotId = snapshot.id;
|
|
97
|
+
space.totals = snapshot.totals;
|
|
98
|
+
space.issues = snapshot.issues;
|
|
99
|
+
const cleanupLogs = await CleanupJournal.inspectPending();
|
|
100
|
+
for (const journal of cleanupLogs.journals) {
|
|
101
|
+
const data = journal.getData();
|
|
102
|
+
space.issues.push({
|
|
103
|
+
kind: 'recovery-log',
|
|
104
|
+
id: { libName: 'cleanup-journal', commit: data.planId },
|
|
105
|
+
message: `存在待恢复清理日志,组状态: ${data.groups.map((group) => `${group.index}:${group.state}`).join(', ')}`,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
for (const filePath of cleanupLogs.corruptedFiles) {
|
|
109
|
+
space.issues.push({
|
|
110
|
+
kind: 'recovery-log',
|
|
111
|
+
id: { libName: 'cleanup-journal', commit: path.basename(filePath) },
|
|
112
|
+
message: `清理日志损坏,已保留原文件: ${filePath}`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const migrations = await MigrationJournal.listPending();
|
|
117
|
+
for (const migration of migrations) {
|
|
118
|
+
const data = migration.getData();
|
|
119
|
+
space.issues.push({
|
|
120
|
+
kind: 'recovery-log',
|
|
121
|
+
id: { libName: 'migration-journal', commit: data.id },
|
|
122
|
+
message: `存在待恢复迁移: ${data.sourcePath} -> ${data.destinationPath},阶段 ${data.phase}`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (migrationError) {
|
|
127
|
+
space.issues.push({
|
|
128
|
+
kind: 'recovery-log',
|
|
129
|
+
id: { libName: 'migration-journal', commit: 'corrupted' },
|
|
130
|
+
message: migrationError.message,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
debug(`[space] audit 完成: snapshot=${snapshot.id}, physical=${snapshot.totals.physical.bytes}, registered=${snapshot.totals.registered.bytes}, difference=${snapshot.totals.differenceBytes}, issues=${space.issues.length}, pendingCleanupLogs=${cleanupLogs.journals.length}, corruptedCleanupLogs=${cleanupLogs.corruptedFiles.length}`);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
space.error = err.message;
|
|
137
|
+
debug(`[space] audit 失败: error=${space.error}`);
|
|
138
|
+
}
|
|
70
139
|
// 计算汇总
|
|
71
140
|
// 错误:检查项失败(ok === false),排除仅作为警告的项
|
|
72
141
|
// 警告:目前只有 disk 有 warn 字段(空间 < 5GB 但不影响功能)
|
|
@@ -83,19 +152,22 @@ async function collectAllIssues(options) {
|
|
|
83
152
|
integrity.missingLibraries.length +
|
|
84
153
|
integrity.staleReferences.length +
|
|
85
154
|
integrity.corruptedStores.length;
|
|
86
|
-
const reclaimableSize =
|
|
155
|
+
const reclaimableSize = space.totals?.reclaimable.bytes
|
|
156
|
+
?? integrity.orphanLibraries.reduce((sum, lib) => sum + lib.size, 0);
|
|
87
157
|
return {
|
|
88
158
|
environment,
|
|
89
159
|
integrity,
|
|
160
|
+
space,
|
|
90
161
|
summary: {
|
|
91
162
|
envErrors,
|
|
92
163
|
envWarnings,
|
|
93
164
|
integrityIssues,
|
|
94
165
|
reclaimableSize,
|
|
166
|
+
spaceIssues: space.issues.length,
|
|
95
167
|
},
|
|
96
168
|
};
|
|
97
169
|
}
|
|
98
|
-
async function checkEnvironment() {
|
|
170
|
+
async function checkEnvironment(readOnly = false) {
|
|
99
171
|
const codepacEnvironment = await checkCodepacEnvironment();
|
|
100
172
|
const result = {
|
|
101
173
|
codepac: { ok: false, message: '', details: codepacEnvironment },
|
|
@@ -111,7 +183,7 @@ async function checkEnvironment() {
|
|
|
111
183
|
details: codepacEnvironment,
|
|
112
184
|
};
|
|
113
185
|
// 2. 配置
|
|
114
|
-
const cfg = await config.load();
|
|
186
|
+
const cfg = await config.load({ migrate: !readOnly });
|
|
115
187
|
result.config = {
|
|
116
188
|
ok: !!cfg,
|
|
117
189
|
message: cfg ? '已初始化' : '未初始化,运行 td init',
|
|
@@ -179,7 +251,7 @@ async function checkIntegrity(options) {
|
|
|
179
251
|
let storePath;
|
|
180
252
|
try {
|
|
181
253
|
registry = getRegistry();
|
|
182
|
-
await registry.load();
|
|
254
|
+
await registry.load({ migrate: !options?.readOnly });
|
|
183
255
|
storePath = await store.getStorePath();
|
|
184
256
|
}
|
|
185
257
|
catch (err) {
|
|
@@ -348,7 +420,7 @@ async function checkIntegrity(options) {
|
|
|
348
420
|
if (options?.integrity) {
|
|
349
421
|
const stores = registry.listStores();
|
|
350
422
|
for (const entry of stores) {
|
|
351
|
-
if (entry.fileCount != null) {
|
|
423
|
+
if (entry.fileCount != null && entry.contentHash) {
|
|
352
424
|
// 有 fileCount 的做 fullVerify
|
|
353
425
|
const expectedData = {
|
|
354
426
|
size: entry.size,
|
|
@@ -368,23 +440,17 @@ async function checkIntegrity(options) {
|
|
|
368
440
|
}
|
|
369
441
|
}
|
|
370
442
|
else {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
381
|
-
catch {
|
|
382
|
-
debug(`[check] 回填完整性数据失败: ${entry.libName}:${entry.commit}:${entry.platform}`);
|
|
383
|
-
}
|
|
443
|
+
result.corruptedStores.push({
|
|
444
|
+
libName: entry.libName,
|
|
445
|
+
commit: entry.commit,
|
|
446
|
+
platform: entry.platform,
|
|
447
|
+
kind: 'missing_integrity_record',
|
|
448
|
+
reason: 'missing integrity record',
|
|
449
|
+
expected: { size: entry.size },
|
|
450
|
+
actual: { fileCount: 0, size: 0 },
|
|
451
|
+
});
|
|
384
452
|
}
|
|
385
453
|
}
|
|
386
|
-
// 保存回填的数据
|
|
387
|
-
await registry.save();
|
|
388
454
|
}
|
|
389
455
|
return result;
|
|
390
456
|
}
|
|
@@ -426,10 +492,28 @@ function renderReport(result) {
|
|
|
426
492
|
? '完整'
|
|
427
493
|
: `${integ.corruptedStores.length} 个损坏的 Store 条目`, false);
|
|
428
494
|
blank();
|
|
495
|
+
info('空间一致性');
|
|
496
|
+
separator();
|
|
497
|
+
if (result.space.error) {
|
|
498
|
+
renderCheck('空间审计', false, result.space.error, false);
|
|
499
|
+
}
|
|
500
|
+
else if (result.space.totals) {
|
|
501
|
+
const totals = result.space.totals;
|
|
502
|
+
renderCheck('空间审计', result.space.issues.length === 0, result.space.issues.length === 0
|
|
503
|
+
? `实际占用 ${formatSize(totals.physical.bytes)},登记容量一致`
|
|
504
|
+
: `${result.space.issues.length} 个差异,实际 ${formatSize(totals.physical.bytes)},登记 ${formatSize(totals.registered.bytes)}`, false);
|
|
505
|
+
for (const issue of result.space.issues.slice(0, 20)) {
|
|
506
|
+
debug(`[space] audit 差异: kind=${issue.kind}, lib=${issue.id.libName}, commit=${issue.id.commit}, platform=${issue.platform ?? 'all'}, registered=${issue.registeredBytes ?? 'unknown'}, measured=${issue.measuredBytes ?? 'unknown'}, message=${issue.message}`);
|
|
507
|
+
}
|
|
508
|
+
if (result.space.issues.length > 20) {
|
|
509
|
+
debug(`[space] audit 差异省略: remaining=${result.space.issues.length - 20}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
blank();
|
|
429
513
|
separator();
|
|
430
514
|
// 汇总
|
|
431
|
-
const { envErrors, envWarnings, integrityIssues, reclaimableSize } = result.summary;
|
|
432
|
-
if (envErrors === 0 && envWarnings === 0 && integrityIssues === 0) {
|
|
515
|
+
const { envErrors, envWarnings, integrityIssues, reclaimableSize, spaceIssues } = result.summary;
|
|
516
|
+
if (envErrors === 0 && envWarnings === 0 && integrityIssues === 0 && spaceIssues === 0) {
|
|
433
517
|
success('系统健康,无问题');
|
|
434
518
|
}
|
|
435
519
|
else {
|
|
@@ -440,6 +524,8 @@ function renderReport(result) {
|
|
|
440
524
|
parts.push(`${envWarnings} 个警告`);
|
|
441
525
|
if (integrityIssues > 0)
|
|
442
526
|
parts.push(`${integrityIssues} 个数据问题`);
|
|
527
|
+
if (spaceIssues > 0)
|
|
528
|
+
parts.push(`${spaceIssues} 个空间差异`);
|
|
443
529
|
if (reclaimableSize > 0)
|
|
444
530
|
parts.push(`可回收 ${formatSize(reclaimableSize)}`);
|
|
445
531
|
warn(`发现问题: ${parts.join(', ')}`);
|
|
@@ -696,15 +782,68 @@ async function fixAllIssues(issues, options) {
|
|
|
696
782
|
separator();
|
|
697
783
|
info('正在修复...');
|
|
698
784
|
blank();
|
|
785
|
+
await withGlobalLock(() => applyAllIssueFixes(issues));
|
|
786
|
+
}
|
|
787
|
+
function commitKey(libName, commit) {
|
|
788
|
+
return JSON.stringify([libName, commit]);
|
|
789
|
+
}
|
|
790
|
+
function buildCorruptedSelections(corrupted, includeShared) {
|
|
791
|
+
const grouped = new Map();
|
|
792
|
+
for (const item of corrupted) {
|
|
793
|
+
const key = commitKey(item.libName, item.commit);
|
|
794
|
+
const group = grouped.get(key) ?? {
|
|
795
|
+
id: { libName: item.libName, commit: item.commit },
|
|
796
|
+
platforms: new Set(),
|
|
797
|
+
};
|
|
798
|
+
if (!includeShared)
|
|
799
|
+
group.platforms.add(item.platform);
|
|
800
|
+
grouped.set(key, group);
|
|
801
|
+
}
|
|
802
|
+
return [...grouped.values()].map((group) => ({
|
|
803
|
+
id: group.id,
|
|
804
|
+
platforms: includeShared ? 'all' : [...group.platforms],
|
|
805
|
+
...(includeShared ? { includeShared: true } : {}),
|
|
806
|
+
}));
|
|
807
|
+
}
|
|
808
|
+
async function executeRepairPlan(spaceService, executor, selections, options) {
|
|
809
|
+
if (selections.length === 0)
|
|
810
|
+
return null;
|
|
811
|
+
const plan = await spaceService.buildCleanupPlan({
|
|
812
|
+
reason: 'repair',
|
|
813
|
+
selections,
|
|
814
|
+
requireUnreferenced: options.requireUnreferenced,
|
|
815
|
+
allowUnknownContent: options.allowUnknownContent,
|
|
816
|
+
});
|
|
817
|
+
for (const warningMessage of plan.warnings)
|
|
818
|
+
warn(`[space] ${warningMessage}`);
|
|
819
|
+
debug(`[check] 调用统一清理执行器: category=${options.category}, plan=${plan.id}, selections=${selections.length}, groups=${plan.groups.length}, expected=${plan.expectedRelease.bytes}`);
|
|
820
|
+
if (plan.groups.length === 0)
|
|
821
|
+
return null;
|
|
822
|
+
const result = await executor.execute(plan);
|
|
823
|
+
debug(`[check] 清理执行返回: category=${options.category}, plan=${plan.id}, status=${result.status}, succeeded=${result.succeeded.length}, failed=${result.failed.length}, released=${result.released.bytes}`);
|
|
824
|
+
return result;
|
|
825
|
+
}
|
|
826
|
+
function resolvedIssueCount(result, issues) {
|
|
827
|
+
if (!result)
|
|
828
|
+
return 0;
|
|
829
|
+
return issues.filter((issue) => result.succeeded.some((group) => group.id.libName === issue.libName
|
|
830
|
+
&& group.id.commit === issue.commit
|
|
831
|
+
&& (group.platform == null || issue.platform == null || group.platform === issue.platform))).length;
|
|
832
|
+
}
|
|
833
|
+
async function applyAllIssueFixes(issues) {
|
|
699
834
|
const registry = getRegistry();
|
|
700
835
|
await registry.load();
|
|
701
836
|
const storePath = await store.getStorePath();
|
|
837
|
+
const spaceService = new SpaceService(registry, storePath);
|
|
838
|
+
const executor = spaceService.createCleanupExecutor();
|
|
839
|
+
const recovered = await executor.recoverPending();
|
|
840
|
+
debug(`[check] 修复开始: invalidProjects=${issues.invalidProjects.length}, danglingLinks=${issues.danglingLinks.length}, orphanLibraries=${issues.orphanLibraries.length}, staleReferences=${issues.staleReferences.length}, corruptedStores=${issues.corruptedStores.length}, recovered=${recovered}`);
|
|
702
841
|
let fixed = 0;
|
|
703
842
|
// 1. 清理无效项目
|
|
704
843
|
for (const p of issues.invalidProjects) {
|
|
705
844
|
try {
|
|
706
845
|
registry.removeProject(p.hash);
|
|
707
|
-
success(
|
|
846
|
+
success(`清理项目: ${p.path}`);
|
|
708
847
|
fixed++;
|
|
709
848
|
}
|
|
710
849
|
catch (err) {
|
|
@@ -729,23 +868,28 @@ async function fixAllIssues(issues, options) {
|
|
|
729
868
|
error(`[err] 移除链接失败: ${link.path} - ${err.message}`);
|
|
730
869
|
}
|
|
731
870
|
}
|
|
732
|
-
// 3.
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
871
|
+
// 3. 删除孤立库。显式修复允许清理该版本中的未登记内容。
|
|
872
|
+
const orphanSelections = [...new Map(issues.orphanLibraries.map((lib) => [
|
|
873
|
+
commitKey(lib.libName, lib.commit),
|
|
874
|
+
{ id: { libName: lib.libName, commit: lib.commit }, platforms: 'all' },
|
|
875
|
+
])).values()];
|
|
876
|
+
try {
|
|
877
|
+
const result = await executeRepairPlan(spaceService, executor, orphanSelections, {
|
|
878
|
+
requireUnreferenced: true,
|
|
879
|
+
allowUnknownContent: true,
|
|
880
|
+
category: 'orphan-library',
|
|
881
|
+
});
|
|
882
|
+
fixed += resolvedIssueCount(result, issues.orphanLibraries);
|
|
883
|
+
for (const group of result?.succeeded ?? []) {
|
|
884
|
+
success(`[ok] 删除孤立库: ${group.id.libName}/${group.id.commit.slice(0, 7)}`);
|
|
744
885
|
}
|
|
745
|
-
|
|
746
|
-
error(`[err] 删除孤立库失败: ${
|
|
886
|
+
for (const failure of result?.failed ?? []) {
|
|
887
|
+
error(`[err] 删除孤立库失败: ${failure.id.libName}/${failure.id.commit.slice(0, 7)} - ${failure.message}`);
|
|
747
888
|
}
|
|
748
889
|
}
|
|
890
|
+
catch (err) {
|
|
891
|
+
error(`[err] 删除孤立库失败: ${err.message}`);
|
|
892
|
+
}
|
|
749
893
|
// 4. 移除失效引用
|
|
750
894
|
for (const ref of issues.staleReferences) {
|
|
751
895
|
try {
|
|
@@ -769,35 +913,51 @@ async function fixAllIssues(issues, options) {
|
|
|
769
913
|
error(`[err] 移除引用失败: ${ref.libKey} - ${err.message}`);
|
|
770
914
|
}
|
|
771
915
|
}
|
|
772
|
-
// 5. 清理损坏的 Store
|
|
773
|
-
|
|
916
|
+
// 5. 清理损坏的 Store 条目。共享冲突整版本处理,其他问题按平台处理。
|
|
917
|
+
const orphanKeys = new Set(issues.orphanLibraries.map((item) => commitKey(item.libName, item.commit)));
|
|
918
|
+
const sharedCorruptions = issues.corruptedStores.filter((item) => (item.kind === 'shared_platform_conflict' || item.platform === SHARED_PLATFORM)
|
|
919
|
+
&& !orphanKeys.has(commitKey(item.libName, item.commit)));
|
|
920
|
+
const sharedCorruptionKeys = new Set(sharedCorruptions.map((item) => commitKey(item.libName, item.commit)));
|
|
921
|
+
const platformCorruptions = issues.corruptedStores.filter((item) => !orphanKeys.has(commitKey(item.libName, item.commit))
|
|
922
|
+
&& !sharedCorruptionKeys.has(commitKey(item.libName, item.commit)));
|
|
923
|
+
for (const cleanup of [
|
|
924
|
+
{
|
|
925
|
+
items: issues.corruptedStores.filter((item) => sharedCorruptionKeys.has(commitKey(item.libName, item.commit))),
|
|
926
|
+
selections: buildCorruptedSelections(sharedCorruptions, true),
|
|
927
|
+
allowUnknownContent: false,
|
|
928
|
+
category: 'shared-corruption',
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
items: platformCorruptions,
|
|
932
|
+
selections: buildCorruptedSelections(platformCorruptions, false),
|
|
933
|
+
allowUnknownContent: false,
|
|
934
|
+
category: 'corrupted-platform',
|
|
935
|
+
},
|
|
936
|
+
]) {
|
|
774
937
|
try {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
938
|
+
const result = await executeRepairPlan(spaceService, executor, cleanup.selections, {
|
|
939
|
+
requireUnreferenced: false,
|
|
940
|
+
allowUnknownContent: cleanup.allowUnknownContent,
|
|
941
|
+
category: cleanup.category,
|
|
942
|
+
});
|
|
943
|
+
fixed += resolvedIssueCount(result, cleanup.items);
|
|
944
|
+
for (const group of result?.succeeded ?? []) {
|
|
945
|
+
const suffix = group.platform ? `/${group.platform}` : ' (移除整个 commit 缓存)';
|
|
946
|
+
success(`清理损坏 Store: ${group.id.libName}/${group.id.commit.slice(0, 7)}${suffix}`);
|
|
783
947
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
registry.removeStore(storeKey);
|
|
787
|
-
const platformPath = path.join(storePath, item.libName, item.commit, item.platform);
|
|
788
|
-
await fs.rm(platformPath, { recursive: true, force: true });
|
|
789
|
-
success(`[ok] 清理损坏 Store: ${item.libName}/${item.commit.slice(0, 7)}/${item.platform}`);
|
|
948
|
+
for (const failure of result?.failed ?? []) {
|
|
949
|
+
error(`清理损坏 Store 失败: ${failure.id.libName}/${failure.id.commit.slice(0, 7)}/${failure.platform ?? 'all'} - ${failure.message}`);
|
|
790
950
|
}
|
|
791
|
-
fixed++;
|
|
792
951
|
}
|
|
793
952
|
catch (err) {
|
|
794
|
-
error(
|
|
953
|
+
error(`清理损坏 Store 失败: category=${cleanup.category} - ${err.message}`);
|
|
795
954
|
}
|
|
796
955
|
}
|
|
797
956
|
await registry.save();
|
|
798
957
|
blank();
|
|
799
958
|
separator();
|
|
800
959
|
success(`修复完成: ${fixed} 个问题已解决`);
|
|
960
|
+
debug(`[check] 修复完成: fixed=${fixed}`);
|
|
801
961
|
}
|
|
802
962
|
// ============ 工具函数 ============
|
|
803
963
|
async function getDirSizeRecursive(dirPath) {
|
|
@@ -902,7 +1062,7 @@ export async function checkStoreIntegrity() {
|
|
|
902
1062
|
});
|
|
903
1063
|
}
|
|
904
1064
|
}
|
|
905
|
-
if (entry.fileCount != null) {
|
|
1065
|
+
if (entry.fileCount != null && entry.contentHash) {
|
|
906
1066
|
const expectedData = {
|
|
907
1067
|
size: entry.size,
|
|
908
1068
|
fileCount: entry.fileCount,
|
|
@@ -921,22 +1081,17 @@ export async function checkStoreIntegrity() {
|
|
|
921
1081
|
}
|
|
922
1082
|
}
|
|
923
1083
|
else {
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
}
|
|
934
|
-
catch {
|
|
935
|
-
debug(`[check] 回填完整性数据失败: ${entry.libName}:${entry.commit}:${entry.platform}`);
|
|
936
|
-
}
|
|
1084
|
+
corrupted.push({
|
|
1085
|
+
libName: entry.libName,
|
|
1086
|
+
commit: entry.commit,
|
|
1087
|
+
platform: entry.platform,
|
|
1088
|
+
kind: 'missing_integrity_record',
|
|
1089
|
+
reason: 'missing integrity record',
|
|
1090
|
+
expected: { size: entry.size },
|
|
1091
|
+
actual: { fileCount: 0, size: 0 },
|
|
1092
|
+
});
|
|
937
1093
|
}
|
|
938
1094
|
}
|
|
939
|
-
await registry.save();
|
|
940
1095
|
return corrupted;
|
|
941
1096
|
}
|
|
942
1097
|
/**
|
|
@@ -948,30 +1103,42 @@ export async function checkStoreIntegrity() {
|
|
|
948
1103
|
export async function fixCorruptedStores(corrupted) {
|
|
949
1104
|
if (corrupted.length === 0)
|
|
950
1105
|
return;
|
|
1106
|
+
await withGlobalLock(() => applyCorruptedStoreFixes(corrupted));
|
|
1107
|
+
}
|
|
1108
|
+
async function applyCorruptedStoreFixes(corrupted) {
|
|
951
1109
|
const registry = getRegistry();
|
|
952
1110
|
await registry.load();
|
|
953
1111
|
const storePath = await store.getStorePath();
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1112
|
+
const spaceService = new SpaceService(registry, storePath);
|
|
1113
|
+
const executor = spaceService.createCleanupExecutor();
|
|
1114
|
+
const recovered = await executor.recoverPending();
|
|
1115
|
+
debug(`[check] 兼容修复入口开始: corruptedStores=${corrupted.length}, recovered=${recovered}`);
|
|
1116
|
+
const sharedCorruptions = corrupted.filter((item) => item.kind === 'shared_platform_conflict' || item.platform === SHARED_PLATFORM);
|
|
1117
|
+
const sharedCorruptionKeys = new Set(sharedCorruptions.map((item) => commitKey(item.libName, item.commit)));
|
|
1118
|
+
const platformCorruptions = corrupted.filter((item) => !sharedCorruptionKeys.has(commitKey(item.libName, item.commit)));
|
|
1119
|
+
for (const cleanup of [
|
|
1120
|
+
{
|
|
1121
|
+
selections: buildCorruptedSelections(sharedCorruptions, true),
|
|
1122
|
+
allowUnknownContent: false,
|
|
1123
|
+
category: 'shared-corruption',
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
selections: buildCorruptedSelections(platformCorruptions, false),
|
|
1127
|
+
allowUnknownContent: false,
|
|
1128
|
+
category: 'corrupted-platform',
|
|
1129
|
+
},
|
|
1130
|
+
]) {
|
|
1131
|
+
const result = await executeRepairPlan(spaceService, executor, cleanup.selections, {
|
|
1132
|
+
requireUnreferenced: false,
|
|
1133
|
+
allowUnknownContent: cleanup.allowUnknownContent,
|
|
1134
|
+
category: cleanup.category,
|
|
1135
|
+
});
|
|
1136
|
+
for (const group of result?.succeeded ?? []) {
|
|
1137
|
+
const suffix = group.platform ? `/${group.platform}` : ' (移除整个 commit 缓存)';
|
|
1138
|
+
success(`清理损坏 Store: ${group.id.libName}/${group.id.commit.slice(0, 7)}${suffix}`);
|
|
972
1139
|
}
|
|
973
|
-
|
|
974
|
-
error(
|
|
1140
|
+
for (const failure of result?.failed ?? []) {
|
|
1141
|
+
error(`清理损坏 Store 失败: ${failure.id.libName}/${failure.id.commit.slice(0, 7)}/${failure.platform ?? 'all'} - ${failure.message}`);
|
|
975
1142
|
}
|
|
976
1143
|
}
|
|
977
1144
|
await registry.save();
|