tanmi-dock 1.0.4 → 1.0.5-beta
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/dist/commands/check.d.ts +30 -4
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +453 -84
- package/dist/commands/check.js.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +4 -3
- package/dist/core/config.js.map +1 -1
- package/dist/core/space/calibration-executor.d.ts +2 -1
- package/dist/core/space/calibration-executor.d.ts.map +1 -1
- package/dist/core/space/calibration-executor.js +2 -0
- package/dist/core/space/calibration-executor.js.map +1 -1
- package/dist/core/space/inventory.d.ts +2 -1
- package/dist/core/space/inventory.d.ts.map +1 -1
- package/dist/core/space/inventory.js +23 -0
- package/dist/core/space/inventory.js.map +1 -1
- package/dist/core/space/service.d.ts +2 -2
- package/dist/core/space/service.d.ts.map +1 -1
- package/dist/core/space/service.js +2 -2
- package/dist/core/space/service.js.map +1 -1
- package/dist/core/space/types.d.ts +7 -0
- package/dist/core/space/types.d.ts.map +1 -1
- package/dist/core/space/types.js.map +1 -1
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +43 -3
- package/dist/utils/logger.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/check.js
CHANGED
|
@@ -9,18 +9,91 @@ 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, SHARED_PLATFORM } from '../core/platform.js';
|
|
12
|
+
import { GENERAL_PLATFORM, KNOWN_PLATFORM_VALUES, SHARED_PLATFORM } from '../core/platform.js';
|
|
13
13
|
import { SpaceService } from '../core/space/service.js';
|
|
14
14
|
import { CleanupJournal } from '../core/space/cleanup-journal.js';
|
|
15
15
|
import { MigrationJournal } from '../core/space/migration-journal.js';
|
|
16
16
|
import { withGlobalLock } from '../utils/global-lock.js';
|
|
17
|
-
import { info, warn, success, error, hint, blank, title, separator, colorize, debug, } from '../utils/logger.js';
|
|
17
|
+
import { info, warn, success, error, hint, blank, title, separator, colorize, debug, withStdoutLogsSuppressed, } from '../utils/logger.js';
|
|
18
18
|
import { selectWithCancel, checkboxWithCancel, confirmWithCancel, PROMPT_CANCELLED, } from '../utils/prompt.js';
|
|
19
|
+
function createSpaceProgressRenderer(label) {
|
|
20
|
+
let lineWidth = 0;
|
|
21
|
+
let lastReported = -1;
|
|
22
|
+
const close = () => {
|
|
23
|
+
if (lineWidth > 0 && process.stdout.isTTY) {
|
|
24
|
+
process.stdout.write(`\r${' '.repeat(lineWidth)}\r`);
|
|
25
|
+
}
|
|
26
|
+
lineWidth = 0;
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
update(progress) {
|
|
30
|
+
if (progress.phase === 'completed') {
|
|
31
|
+
close();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const message = progress.phase === 'measure-store'
|
|
35
|
+
? `${label}: 正在汇总 Store 总占用...`
|
|
36
|
+
: `${label}: ${progress.completedCommits}/${progress.totalCommits} 个版本`;
|
|
37
|
+
if (process.stdout.isTTY) {
|
|
38
|
+
const padded = message.padEnd(Math.max(lineWidth, message.length));
|
|
39
|
+
lineWidth = padded.length;
|
|
40
|
+
process.stdout.write(`\r${padded}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const interval = Math.max(1, Math.ceil(progress.totalCommits / 10));
|
|
44
|
+
const shouldReport = progress.phase === 'measure-store' ||
|
|
45
|
+
progress.completedCommits === 0 ||
|
|
46
|
+
progress.completedCommits === progress.totalCommits ||
|
|
47
|
+
progress.completedCommits - lastReported >= interval;
|
|
48
|
+
if (shouldReport && progress.completedCommits !== lastReported) {
|
|
49
|
+
info(message);
|
|
50
|
+
lastReported = progress.completedCommits;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
close,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function createCheckProgressRenderer() {
|
|
57
|
+
const spaceProgress = createSpaceProgressRenderer('[3/3] 正在审计 Store 空间');
|
|
58
|
+
return (progress) => {
|
|
59
|
+
if (progress.stage === 'space' && progress.state === 'progress') {
|
|
60
|
+
spaceProgress.update(progress.progress);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (progress.stage === 'space')
|
|
64
|
+
spaceProgress.close();
|
|
65
|
+
const indexes = { environment: 1, integrity: 2, space: 3 };
|
|
66
|
+
if (progress.state === 'started') {
|
|
67
|
+
const messages = {
|
|
68
|
+
environment: '正在检查运行环境...',
|
|
69
|
+
integrity: '正在核对项目与依赖...',
|
|
70
|
+
space: '正在审计 Store 空间...',
|
|
71
|
+
};
|
|
72
|
+
info(`[${indexes[progress.stage]}/3] ${messages[progress.stage]}`);
|
|
73
|
+
}
|
|
74
|
+
else if (progress.state === 'completed') {
|
|
75
|
+
const messages = {
|
|
76
|
+
environment: '运行环境检查完成',
|
|
77
|
+
integrity: '项目与依赖核对完成',
|
|
78
|
+
space: 'Store 空间审计完成',
|
|
79
|
+
};
|
|
80
|
+
info(`[${indexes[progress.stage]}/3] ${messages[progress.stage]}`);
|
|
81
|
+
}
|
|
82
|
+
else if (progress.state === 'failed') {
|
|
83
|
+
const labels = {
|
|
84
|
+
environment: '运行环境',
|
|
85
|
+
integrity: '项目与依赖',
|
|
86
|
+
space: 'Store 空间',
|
|
87
|
+
};
|
|
88
|
+
warn(`[${indexes[progress.stage]}/3] ${labels[progress.stage]}检查失败: ${progress.error ?? '未知错误'}`);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
19
92
|
// ============ 命令创建 ============
|
|
20
93
|
export function createCheckCommand() {
|
|
21
94
|
return new Command('check')
|
|
22
95
|
.description('健康检查(环境诊断 + 数据一致性验证)')
|
|
23
|
-
.option('--fix', '
|
|
96
|
+
.option('--fix', '直接修复所有可修复问题')
|
|
24
97
|
.option('--dry-run', '只显示问题,不修复')
|
|
25
98
|
.option('--json', '输出 JSON 格式')
|
|
26
99
|
.option('--force', '跳过确认')
|
|
@@ -31,6 +104,14 @@ export function createCheckCommand() {
|
|
|
31
104
|
}
|
|
32
105
|
// ============ 主流程 ============
|
|
33
106
|
async function runCheck(options) {
|
|
107
|
+
if (options.json) {
|
|
108
|
+
await withStdoutLogsSuppressed(() => runCheckInternal(options));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
await runCheckInternal(options);
|
|
112
|
+
}
|
|
113
|
+
async function runCheckInternal(options) {
|
|
114
|
+
debug(`[check] 命令输入: fix=${options.fix}, dryRun=${options.dryRun}, json=${options.json}, force=${options.force}, integrity=${options.integrity}`);
|
|
34
115
|
if (options.dryRun) {
|
|
35
116
|
const cfg = await config.load({ migrate: false });
|
|
36
117
|
if (cfg && config.checkConfigVersion(cfg) === 'migrate') {
|
|
@@ -41,6 +122,7 @@ async function runCheck(options) {
|
|
|
41
122
|
const result = await collectAllIssues({
|
|
42
123
|
integrity: options.integrity,
|
|
43
124
|
readOnly: options.dryRun,
|
|
125
|
+
onProgress: options.json ? undefined : createCheckProgressRenderer(),
|
|
44
126
|
});
|
|
45
127
|
// JSON 输出
|
|
46
128
|
if (options.json) {
|
|
@@ -50,13 +132,14 @@ async function runCheck(options) {
|
|
|
50
132
|
// 渲染报告
|
|
51
133
|
renderReport(result);
|
|
52
134
|
const hasIntegrityIssues = result.summary.integrityIssues > 0;
|
|
53
|
-
const
|
|
135
|
+
const hasReportedIssues = hasIntegrityIssues || result.summary.spaceIssues > 0;
|
|
136
|
+
const hasRepairableIssues = hasIntegrityIssues || hasSpaceCalibrationChoice(result.space);
|
|
54
137
|
const hasEnvErrors = result.summary.envErrors > 0;
|
|
55
138
|
// dry-run 模式
|
|
56
139
|
if (options.dryRun) {
|
|
57
|
-
if (
|
|
140
|
+
if (hasRepairableIssues) {
|
|
58
141
|
blank();
|
|
59
|
-
hint('运行 td check --fix
|
|
142
|
+
hint('运行 td check --fix 修复可修复问题');
|
|
60
143
|
}
|
|
61
144
|
return;
|
|
62
145
|
}
|
|
@@ -67,35 +150,48 @@ async function runCheck(options) {
|
|
|
67
150
|
return;
|
|
68
151
|
}
|
|
69
152
|
// 无问题
|
|
70
|
-
if (!
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
hint('空间差异需要通过详细检查确认,本次没有自动修改注册记录');
|
|
153
|
+
if (!hasRepairableIssues) {
|
|
154
|
+
if (hasReportedIssues) {
|
|
155
|
+
debug('[check] 检查结果只包含无自动修复动作的问题');
|
|
156
|
+
}
|
|
75
157
|
return;
|
|
76
158
|
}
|
|
77
159
|
// --fix 模式:直接修复
|
|
78
160
|
if (options.fix) {
|
|
79
|
-
await fixAllIssues(result.integrity,
|
|
161
|
+
await fixAllIssues(result.integrity, result.space, {
|
|
162
|
+
force: options.force,
|
|
163
|
+
calibrateSpace: hasSpaceCalibrationChoice(result.space),
|
|
164
|
+
deleteUnregistered: false,
|
|
165
|
+
verifyStoreIntegrity: options.integrity,
|
|
166
|
+
});
|
|
167
|
+
renderUncachedDependencyHint(result.integrity);
|
|
80
168
|
return;
|
|
81
169
|
}
|
|
82
170
|
// 交互模式
|
|
83
|
-
await interactiveCheck(result
|
|
171
|
+
await interactiveCheck(result, options);
|
|
84
172
|
}
|
|
85
173
|
// ============ 数据收集 ============
|
|
86
|
-
async function collectAllIssues(options) {
|
|
174
|
+
async function collectAllIssues(options = {}) {
|
|
175
|
+
debug(`[check] 健康检查开始: integrity=${options.integrity ?? false}, readOnly=${options.readOnly ?? false}, progress=${options.onProgress ? 'enabled' : 'disabled'}`);
|
|
176
|
+
options.onProgress?.({ stage: 'environment', state: 'started' });
|
|
87
177
|
const environment = await checkEnvironment(options?.readOnly);
|
|
178
|
+
options.onProgress?.({ stage: 'environment', state: 'completed' });
|
|
179
|
+
options.onProgress?.({ stage: 'integrity', state: 'started' });
|
|
88
180
|
const integrity = await checkIntegrity(options);
|
|
181
|
+
options.onProgress?.({ stage: 'integrity', state: 'completed' });
|
|
89
182
|
const space = { issues: [] };
|
|
183
|
+
options.onProgress?.({ stage: 'space', state: 'started' });
|
|
90
184
|
try {
|
|
91
185
|
const registry = getRegistry();
|
|
92
186
|
await registry.load({ migrate: !options?.readOnly });
|
|
93
187
|
const storePath = await store.getStorePath();
|
|
94
188
|
debug(`[space] audit 开始: store=${storePath}, source=td-check`);
|
|
95
|
-
const
|
|
189
|
+
const spaceService = new SpaceService(registry, storePath);
|
|
190
|
+
const snapshot = await spaceService.buildSnapshot('audit', undefined, (progress) => options.onProgress?.({ stage: 'space', state: 'progress', progress }));
|
|
96
191
|
space.snapshotId = snapshot.id;
|
|
97
192
|
space.totals = snapshot.totals;
|
|
98
193
|
space.issues = snapshot.issues;
|
|
194
|
+
space.calibrationPlan = spaceService.buildCalibrationPlan(snapshot);
|
|
99
195
|
const cleanupLogs = await CleanupJournal.inspectPending();
|
|
100
196
|
for (const journal of cleanupLogs.journals) {
|
|
101
197
|
const data = journal.getData();
|
|
@@ -131,10 +227,12 @@ async function collectAllIssues(options) {
|
|
|
131
227
|
});
|
|
132
228
|
}
|
|
133
229
|
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}`);
|
|
230
|
+
options.onProgress?.({ stage: 'space', state: 'completed' });
|
|
134
231
|
}
|
|
135
232
|
catch (err) {
|
|
136
233
|
space.error = err.message;
|
|
137
234
|
debug(`[space] audit 失败: error=${space.error}`);
|
|
235
|
+
options.onProgress?.({ stage: 'space', state: 'failed', error: space.error });
|
|
138
236
|
}
|
|
139
237
|
// 计算汇总
|
|
140
238
|
// 错误:检查项失败(ok === false),排除仅作为警告的项
|
|
@@ -149,12 +247,11 @@ async function collectAllIssues(options) {
|
|
|
149
247
|
const integrityIssues = integrity.invalidProjects.length +
|
|
150
248
|
integrity.danglingLinks.length +
|
|
151
249
|
integrity.orphanLibraries.length +
|
|
152
|
-
integrity.missingLibraries.length +
|
|
153
250
|
integrity.staleReferences.length +
|
|
154
251
|
integrity.corruptedStores.length;
|
|
155
252
|
const reclaimableSize = space.totals?.reclaimable.bytes
|
|
156
253
|
?? integrity.orphanLibraries.reduce((sum, lib) => sum + lib.size, 0);
|
|
157
|
-
|
|
254
|
+
const result = {
|
|
158
255
|
environment,
|
|
159
256
|
integrity,
|
|
160
257
|
space,
|
|
@@ -162,10 +259,13 @@ async function collectAllIssues(options) {
|
|
|
162
259
|
envErrors,
|
|
163
260
|
envWarnings,
|
|
164
261
|
integrityIssues,
|
|
262
|
+
uncachedDependencies: integrity.missingLibraries.length,
|
|
165
263
|
reclaimableSize,
|
|
166
264
|
spaceIssues: space.issues.length,
|
|
167
265
|
},
|
|
168
266
|
};
|
|
267
|
+
debug(`[check] 健康检查完成: envErrors=${envErrors}, envWarnings=${envWarnings}, integrityIssues=${integrityIssues}, uncachedDependencies=${integrity.missingLibraries.length}, spaceIssues=${space.issues.length}, spaceSnapshot=${space.snapshotId ?? 'none'}, spaceError=${space.error ?? 'none'}`);
|
|
268
|
+
return result;
|
|
169
269
|
}
|
|
170
270
|
async function checkEnvironment(readOnly = false) {
|
|
171
271
|
const codepacEnvironment = await checkCodepacEnvironment();
|
|
@@ -260,6 +360,7 @@ async function checkIntegrity(options) {
|
|
|
260
360
|
return result;
|
|
261
361
|
}
|
|
262
362
|
const projects = registry.listProjects();
|
|
363
|
+
debug(`[check] 数据一致性检查开始: store=${storePath}, projects=${projects.length}, integrity=${options?.integrity ?? false}, readOnly=${options?.readOnly ?? false}`);
|
|
263
364
|
// 1. 检查项目和链接
|
|
264
365
|
for (const project of projects) {
|
|
265
366
|
const projectHash = registry.hashPath(project.path);
|
|
@@ -279,6 +380,25 @@ async function checkIntegrity(options) {
|
|
|
279
380
|
if (!verifyPlatform) {
|
|
280
381
|
continue;
|
|
281
382
|
}
|
|
383
|
+
const libraryKey = registry.getLibraryKey(dep.libName, dep.commit);
|
|
384
|
+
const library = registry.getLibrary(libraryKey);
|
|
385
|
+
const hasGeneralStoreEntry = registry
|
|
386
|
+
.getLibraryStoreKeys(dep.libName, dep.commit)
|
|
387
|
+
.some((storeKey) => registry.getStore(storeKey)?.platform === GENERAL_PLATFORM);
|
|
388
|
+
const isGeneral = library?.isGeneral === true ||
|
|
389
|
+
library?.platforms.includes(GENERAL_PLATFORM) === true ||
|
|
390
|
+
hasGeneralStoreEntry;
|
|
391
|
+
const cachePlatform = isGeneral ? GENERAL_PLATFORM : verifyPlatform;
|
|
392
|
+
const cacheExists = await store.exists(dep.libName, dep.commit, cachePlatform);
|
|
393
|
+
if (!cacheExists) {
|
|
394
|
+
result.missingLibraries.push({
|
|
395
|
+
libName: dep.libName,
|
|
396
|
+
commit: dep.commit,
|
|
397
|
+
project: project.path,
|
|
398
|
+
});
|
|
399
|
+
debug(`[check] 本地依赖缓存缺失: project=${project.path}, lib=${dep.libName}, commit=${dep.commit}, platform=${cachePlatform}, link=${linkPath}`);
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
282
402
|
try {
|
|
283
403
|
const stat = await fs.lstat(linkPath);
|
|
284
404
|
if (stat.isSymbolicLink()) {
|
|
@@ -291,21 +411,19 @@ async function checkIntegrity(options) {
|
|
|
291
411
|
result.danglingLinks.push({
|
|
292
412
|
path: linkPath,
|
|
293
413
|
projectHash,
|
|
294
|
-
dep: {
|
|
414
|
+
dep: {
|
|
415
|
+
libName: dep.libName,
|
|
416
|
+
commit: dep.commit,
|
|
417
|
+
platform: dep.platform,
|
|
418
|
+
linkedPath: dep.linkedPath,
|
|
419
|
+
...(dep.scope ? { scope: dep.scope } : {}),
|
|
420
|
+
},
|
|
295
421
|
});
|
|
296
422
|
}
|
|
297
423
|
}
|
|
298
424
|
}
|
|
299
425
|
catch {
|
|
300
|
-
//
|
|
301
|
-
const exists = await store.exists(dep.libName, dep.commit, verifyPlatform);
|
|
302
|
-
if (!exists) {
|
|
303
|
-
result.missingLibraries.push({
|
|
304
|
-
libName: dep.libName,
|
|
305
|
-
commit: dep.commit,
|
|
306
|
-
project: project.path,
|
|
307
|
-
});
|
|
308
|
-
}
|
|
426
|
+
// Store 缓存存在但项目链接缺失,后续引用检查会报告失效引用。
|
|
309
427
|
}
|
|
310
428
|
}
|
|
311
429
|
}
|
|
@@ -452,6 +570,7 @@ async function checkIntegrity(options) {
|
|
|
452
570
|
}
|
|
453
571
|
}
|
|
454
572
|
}
|
|
573
|
+
debug(`[check] 数据一致性检查完成: invalidProjects=${result.invalidProjects.length}, danglingLinks=${result.danglingLinks.length}, orphanLibraries=${result.orphanLibraries.length}, uncachedDependencies=${result.missingLibraries.length}, staleReferences=${result.staleReferences.length}, corruptedStores=${result.corruptedStores.length}`);
|
|
455
574
|
return result;
|
|
456
575
|
}
|
|
457
576
|
// ============ 报告渲染 ============
|
|
@@ -481,10 +600,9 @@ function renderReport(result) {
|
|
|
481
600
|
renderCheck('孤立库', integ.orphanLibraries.length === 0, integ.orphanLibraries.length === 0
|
|
482
601
|
? '无'
|
|
483
602
|
: `${integ.orphanLibraries.length} 个未登记 (${formatSize(result.summary.reclaimableSize)})`, false);
|
|
484
|
-
renderCheck('
|
|
603
|
+
renderCheck('本地未缓存依赖', integ.missingLibraries.length === 0, integ.missingLibraries.length === 0
|
|
485
604
|
? '无'
|
|
486
|
-
: `${integ.missingLibraries.length}
|
|
487
|
-
);
|
|
605
|
+
: `${integ.missingLibraries.length} 项(项目需要时可运行 td link)`, integ.missingLibraries.length > 0);
|
|
488
606
|
renderCheck('引用关系', integ.staleReferences.length === 0, integ.staleReferences.length === 0
|
|
489
607
|
? '一致'
|
|
490
608
|
: `${integ.staleReferences.length} 个失效引用`, false);
|
|
@@ -512,8 +630,12 @@ function renderReport(result) {
|
|
|
512
630
|
blank();
|
|
513
631
|
separator();
|
|
514
632
|
// 汇总
|
|
515
|
-
const { envErrors, envWarnings, integrityIssues, reclaimableSize, spaceIssues } = result.summary;
|
|
516
|
-
if (envErrors === 0 &&
|
|
633
|
+
const { envErrors, envWarnings, integrityIssues, uncachedDependencies, reclaimableSize, spaceIssues, } = result.summary;
|
|
634
|
+
if (envErrors === 0 &&
|
|
635
|
+
envWarnings === 0 &&
|
|
636
|
+
integrityIssues === 0 &&
|
|
637
|
+
uncachedDependencies === 0 &&
|
|
638
|
+
spaceIssues === 0) {
|
|
517
639
|
success('系统健康,无问题');
|
|
518
640
|
}
|
|
519
641
|
else {
|
|
@@ -524,6 +646,8 @@ function renderReport(result) {
|
|
|
524
646
|
parts.push(`${envWarnings} 个警告`);
|
|
525
647
|
if (integrityIssues > 0)
|
|
526
648
|
parts.push(`${integrityIssues} 个数据问题`);
|
|
649
|
+
if (uncachedDependencies > 0)
|
|
650
|
+
parts.push(`${uncachedDependencies} 项本地未缓存依赖`);
|
|
527
651
|
if (spaceIssues > 0)
|
|
528
652
|
parts.push(`${spaceIssues} 个空间差异`);
|
|
529
653
|
if (reclaimableSize > 0)
|
|
@@ -585,43 +709,55 @@ function renderCheck(name, ok, message, isWarning) {
|
|
|
585
709
|
}
|
|
586
710
|
}
|
|
587
711
|
// ============ 交互式修复 ============
|
|
588
|
-
|
|
712
|
+
function hasSpaceCalibrationChoice(space) {
|
|
713
|
+
const plan = space.calibrationPlan;
|
|
714
|
+
return Boolean(plan && (plan.actions.length > 0 || plan.unregistered.deletableCount > 0));
|
|
715
|
+
}
|
|
716
|
+
function renderUncachedDependencyHint(issues) {
|
|
717
|
+
if (issues.missingLibraries.length === 0)
|
|
718
|
+
return;
|
|
589
719
|
blank();
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
720
|
+
hint(`${issues.missingLibraries.length} 项本地未缓存依赖已保留;项目需要这些依赖时可运行 td link`);
|
|
721
|
+
}
|
|
722
|
+
async function interactiveCheck(result, options) {
|
|
723
|
+
while (true) {
|
|
724
|
+
blank();
|
|
725
|
+
const action = await selectWithCancel({
|
|
726
|
+
message: '选择操作',
|
|
727
|
+
choices: [
|
|
728
|
+
{ name: '修复所有可修复问题', value: 'fix-all' },
|
|
729
|
+
{ name: '选择性修复', value: 'select' },
|
|
730
|
+
{ name: '查看详情', value: 'detail' },
|
|
731
|
+
{ name: colorize('← 退出', 'dim'), value: 'exit' },
|
|
732
|
+
],
|
|
733
|
+
});
|
|
734
|
+
if (action === PROMPT_CANCELLED) {
|
|
735
|
+
info('已取消');
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
debug(`[check] 交互操作: action=${action}`);
|
|
739
|
+
if (action === 'detail') {
|
|
740
|
+
showDetails(result);
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
if (action === 'exit')
|
|
744
|
+
return;
|
|
745
|
+
if (action === 'select') {
|
|
746
|
+
await selectiveFix(result, options);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
await fixAllIssues(result.integrity, result.space, {
|
|
750
|
+
force: true,
|
|
751
|
+
calibrateSpace: hasSpaceCalibrationChoice(result.space),
|
|
752
|
+
deleteUnregistered: false,
|
|
753
|
+
verifyStoreIntegrity: options.integrity,
|
|
754
|
+
});
|
|
755
|
+
renderUncachedDependencyHint(result.integrity);
|
|
602
756
|
return;
|
|
603
757
|
}
|
|
604
|
-
switch (action) {
|
|
605
|
-
case 'fix-all':
|
|
606
|
-
await fixAllIssues(issues, { force: true });
|
|
607
|
-
// 缺失库无法自动修复,需要提示用户
|
|
608
|
-
if (issues.missingLibraries.length > 0) {
|
|
609
|
-
blank();
|
|
610
|
-
hint(`缺失库 (${issues.missingLibraries.length} 个) 需要通过 td link 重新下载`);
|
|
611
|
-
}
|
|
612
|
-
break;
|
|
613
|
-
case 'select':
|
|
614
|
-
await selectiveFix(issues, options);
|
|
615
|
-
break;
|
|
616
|
-
case 'detail':
|
|
617
|
-
showDetails(issues);
|
|
618
|
-
await interactiveCheck(issues, options);
|
|
619
|
-
break;
|
|
620
|
-
case 'exit':
|
|
621
|
-
break;
|
|
622
|
-
}
|
|
623
758
|
}
|
|
624
|
-
async function selectiveFix(
|
|
759
|
+
async function selectiveFix(result, options) {
|
|
760
|
+
const issues = result.integrity;
|
|
625
761
|
// 构建选项
|
|
626
762
|
const choices = [];
|
|
627
763
|
if (issues.invalidProjects.length > 0) {
|
|
@@ -660,6 +796,13 @@ async function selectiveFix(issues, options) {
|
|
|
660
796
|
checked: true,
|
|
661
797
|
});
|
|
662
798
|
}
|
|
799
|
+
if (hasSpaceCalibrationChoice(result.space)) {
|
|
800
|
+
choices.push({
|
|
801
|
+
name: `校准 ${result.space.issues.length} 个空间差异`,
|
|
802
|
+
value: 'spaceCalibration',
|
|
803
|
+
checked: true,
|
|
804
|
+
});
|
|
805
|
+
}
|
|
663
806
|
if (choices.length === 0) {
|
|
664
807
|
success('没有可修复的问题');
|
|
665
808
|
return;
|
|
@@ -687,7 +830,8 @@ async function selectiveFix(issues, options) {
|
|
|
687
830
|
orphanLibraries: selected.includes('orphanLibraries')
|
|
688
831
|
? issues.orphanLibraries
|
|
689
832
|
: [],
|
|
690
|
-
|
|
833
|
+
// 本地未缓存依赖是用户允许的缓存状态,修复流程不下载也不移除依赖记录。
|
|
834
|
+
missingLibraries: [],
|
|
691
835
|
staleReferences: selected.includes('staleReferences')
|
|
692
836
|
? issues.staleReferences
|
|
693
837
|
: [],
|
|
@@ -695,14 +839,76 @@ async function selectiveFix(issues, options) {
|
|
|
695
839
|
? issues.corruptedStores
|
|
696
840
|
: [],
|
|
697
841
|
};
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
842
|
+
const calibrateSpace = selected.includes('spaceCalibration');
|
|
843
|
+
let deleteUnregistered = false;
|
|
844
|
+
const spacePlan = result.space.calibrationPlan;
|
|
845
|
+
if (calibrateSpace &&
|
|
846
|
+
spacePlan &&
|
|
847
|
+
spacePlan.unregistered.totalCount > 0 &&
|
|
848
|
+
spacePlan.unregistered.deletableCount > 0) {
|
|
849
|
+
blank();
|
|
850
|
+
const unregisteredAction = await selectWithCancel({
|
|
851
|
+
message: '未登记平台如何处理?',
|
|
852
|
+
choices: [
|
|
853
|
+
{
|
|
854
|
+
name: '暂不处理',
|
|
855
|
+
value: 'keep',
|
|
856
|
+
description: `保留全部 ${spacePlan.unregistered.totalCount} 项内容`,
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
name: '删除无项目引用的内容',
|
|
860
|
+
value: 'delete-unreferenced',
|
|
861
|
+
description: `删除 ${spacePlan.unregistered.deletableCount} 项,保留 ${spacePlan.unregistered.protectedCount} 项`,
|
|
862
|
+
},
|
|
863
|
+
],
|
|
864
|
+
});
|
|
865
|
+
if (unregisteredAction === PROMPT_CANCELLED) {
|
|
866
|
+
info('已取消');
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
deleteUnregistered = unregisteredAction === 'delete-unreferenced';
|
|
870
|
+
}
|
|
871
|
+
debug(`[check] 选择性修复输入: selected=${selected.join(',')}, calibrateSpace=${calibrateSpace}, unregisteredAction=${deleteUnregistered ? 'delete-unreferenced' : 'keep'}`);
|
|
872
|
+
await fixAllIssues(toFix, result.space, {
|
|
873
|
+
force: options.force,
|
|
874
|
+
calibrateSpace,
|
|
875
|
+
deleteUnregistered,
|
|
876
|
+
verifyStoreIntegrity: options.integrity,
|
|
877
|
+
});
|
|
878
|
+
renderUncachedDependencyHint(issues);
|
|
879
|
+
}
|
|
880
|
+
function formatSignedSize(bytes) {
|
|
881
|
+
const sign = bytes > 0 ? '+' : bytes < 0 ? '-' : '';
|
|
882
|
+
return `${sign}${formatSize(Math.abs(bytes))}`;
|
|
883
|
+
}
|
|
884
|
+
function showSpaceDetails(space) {
|
|
885
|
+
if (space.error) {
|
|
886
|
+
info('空间审计:');
|
|
887
|
+
warn(` ${space.error}`);
|
|
701
888
|
blank();
|
|
702
|
-
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
const plan = space.calibrationPlan;
|
|
892
|
+
if (!plan || space.issues.length === 0)
|
|
893
|
+
return;
|
|
894
|
+
info(`空间差异 (${space.issues.length}):`);
|
|
895
|
+
info(` 登记容量变化 ${plan.summary.changedCount} 项,合计 ${formatSignedSize(plan.summary.changedDifferenceBytes)}`);
|
|
896
|
+
info(` 缺失登记 ${plan.summary.missingCount} 项,登记容量 ${formatSize(plan.summary.missingRegisteredBytes)}`);
|
|
897
|
+
info(` 重复共享登记 ${plan.summary.duplicateSharedCount} 项`);
|
|
898
|
+
info(` 未登记平台 ${plan.unregistered.totalCount} 项,合计 ${formatSize(plan.unregistered.totalBytes)}`);
|
|
899
|
+
if (plan.unregistered.protectedCount > 0) {
|
|
900
|
+
info(` 引用保护 ${plan.unregistered.protectedCount} 项`);
|
|
901
|
+
}
|
|
902
|
+
const otherIssues = space.issues.filter((issue) => !['changed', 'missing', 'duplicate-record', 'unregistered'].includes(issue.kind));
|
|
903
|
+
if (otherIssues.length > 0)
|
|
904
|
+
info(` 其他空间异常 ${otherIssues.length} 项`);
|
|
905
|
+
if (plan.summary.ignoredArtifactCount > 0) {
|
|
906
|
+
info(` 系统文件 ${plan.summary.ignoredArtifactCount} 项,将忽略并保留`);
|
|
703
907
|
}
|
|
908
|
+
blank();
|
|
704
909
|
}
|
|
705
|
-
function showDetails(
|
|
910
|
+
function showDetails(result) {
|
|
911
|
+
const issues = result.integrity;
|
|
706
912
|
blank();
|
|
707
913
|
title('问题详情');
|
|
708
914
|
blank();
|
|
@@ -728,7 +934,7 @@ function showDetails(issues) {
|
|
|
728
934
|
blank();
|
|
729
935
|
}
|
|
730
936
|
if (issues.missingLibraries.length > 0) {
|
|
731
|
-
info(
|
|
937
|
+
info(`本地未缓存依赖 (${issues.missingLibraries.length}):`);
|
|
732
938
|
for (const lib of issues.missingLibraries) {
|
|
733
939
|
hint(` - ${lib.libName}/${lib.commit.slice(0, 7)} (引用自 ${lib.project})`);
|
|
734
940
|
}
|
|
@@ -755,22 +961,61 @@ function showDetails(issues) {
|
|
|
755
961
|
}
|
|
756
962
|
blank();
|
|
757
963
|
}
|
|
964
|
+
showSpaceDetails(result.space);
|
|
758
965
|
}
|
|
759
|
-
|
|
760
|
-
|
|
966
|
+
function integrityRepairCount(issues) {
|
|
967
|
+
return (issues.invalidProjects.length +
|
|
761
968
|
issues.danglingLinks.length +
|
|
762
969
|
issues.orphanLibraries.length +
|
|
763
970
|
issues.staleReferences.length +
|
|
764
|
-
issues.corruptedStores.length;
|
|
765
|
-
|
|
971
|
+
issues.corruptedStores.length);
|
|
972
|
+
}
|
|
973
|
+
function retainCurrentIssues(selected, current, keyOf) {
|
|
974
|
+
const selectedKeys = new Set(selected.map(keyOf));
|
|
975
|
+
return current.filter((issue) => selectedKeys.has(keyOf(issue)));
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* 只保留用户确认过且在取得全局锁后仍然存在的问题。
|
|
979
|
+
* 新出现的问题不会被本次修复附带处理。
|
|
980
|
+
*/
|
|
981
|
+
async function revalidateIntegritySelection(selected, verifyStoreIntegrity) {
|
|
982
|
+
const current = await checkIntegrity({ integrity: verifyStoreIntegrity });
|
|
983
|
+
const revalidated = {
|
|
984
|
+
invalidProjects: retainCurrentIssues(selected.invalidProjects, current.invalidProjects, (issue) => JSON.stringify([issue.hash, issue.path])),
|
|
985
|
+
danglingLinks: retainCurrentIssues(selected.danglingLinks, current.danglingLinks, (issue) => JSON.stringify([
|
|
986
|
+
issue.path,
|
|
987
|
+
issue.projectHash,
|
|
988
|
+
issue.dep.libName,
|
|
989
|
+
issue.dep.commit,
|
|
990
|
+
issue.dep.platform,
|
|
991
|
+
issue.dep.linkedPath,
|
|
992
|
+
issue.dep.scope ?? '',
|
|
993
|
+
])),
|
|
994
|
+
orphanLibraries: retainCurrentIssues(selected.orphanLibraries, current.orphanLibraries, (issue) => JSON.stringify([issue.libName, issue.commit, issue.path])),
|
|
995
|
+
missingLibraries: [],
|
|
996
|
+
staleReferences: retainCurrentIssues(selected.staleReferences, current.staleReferences, (issue) => JSON.stringify([issue.libKey, issue.projectHash])),
|
|
997
|
+
corruptedStores: retainCurrentIssues(selected.corruptedStores, current.corruptedStores, (issue) => JSON.stringify([issue.libName, issue.commit, issue.platform, issue.kind ?? 'integrity'])),
|
|
998
|
+
};
|
|
999
|
+
debug(`[check] 锁内重新检查完成: selected=${integrityRepairCount(selected)}, retained=${integrityRepairCount(revalidated)}, currentUncachedDependencies=${current.missingLibraries.length}, invalidProjects=${revalidated.invalidProjects.length}, danglingLinks=${revalidated.danglingLinks.length}, orphanLibraries=${revalidated.orphanLibraries.length}, staleReferences=${revalidated.staleReferences.length}, corruptedStores=${revalidated.corruptedStores.length}`);
|
|
1000
|
+
return revalidated;
|
|
1001
|
+
}
|
|
1002
|
+
async function fixAllIssues(issues, space, options) {
|
|
1003
|
+
const totalIssues = integrityRepairCount(issues);
|
|
1004
|
+
const spaceIssueCount = options.calibrateSpace ? space.issues.length : 0;
|
|
1005
|
+
if (totalIssues === 0 && spaceIssueCount === 0) {
|
|
766
1006
|
success('没有需要修复的问题');
|
|
767
1007
|
return;
|
|
768
1008
|
}
|
|
1009
|
+
debug(`[check] 修复请求: integrityIssues=${totalIssues}, calibrateSpace=${options.calibrateSpace}, reportedSpaceIssues=${spaceIssueCount}, unregisteredAction=${options.deleteUnregistered ? 'delete-unreferenced' : 'keep'}, force=${options.force}, sourceSnapshot=${space.snapshotId ?? 'none'}`);
|
|
769
1010
|
// 确认
|
|
770
1011
|
if (!options.force) {
|
|
771
1012
|
blank();
|
|
1013
|
+
const targets = [
|
|
1014
|
+
...(totalIssues > 0 ? [`${totalIssues} 个数据问题`] : []),
|
|
1015
|
+
...(spaceIssueCount > 0 ? [`${spaceIssueCount} 个空间差异`] : []),
|
|
1016
|
+
];
|
|
772
1017
|
const confirmed = await confirmWithCancel({
|
|
773
|
-
message:
|
|
1018
|
+
message: `确认处理${targets.join('和')}?`,
|
|
774
1019
|
default: false,
|
|
775
1020
|
});
|
|
776
1021
|
if (confirmed === PROMPT_CANCELLED || !confirmed) {
|
|
@@ -782,7 +1027,100 @@ async function fixAllIssues(issues, options) {
|
|
|
782
1027
|
separator();
|
|
783
1028
|
info('正在修复...');
|
|
784
1029
|
blank();
|
|
785
|
-
await withGlobalLock(() =>
|
|
1030
|
+
await withGlobalLock(async () => {
|
|
1031
|
+
if (totalIssues > 0) {
|
|
1032
|
+
info('正在重新确认待修复的数据问题...');
|
|
1033
|
+
const revalidated = await revalidateIntegritySelection(issues, options.verifyStoreIntegrity);
|
|
1034
|
+
if (integrityRepairCount(revalidated) === 0) {
|
|
1035
|
+
success('待修复的数据问题状态已变化,本次无需处理');
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1038
|
+
const repairResult = await applyAllIssueFixes(revalidated);
|
|
1039
|
+
if (repairResult.failed > 0)
|
|
1040
|
+
process.exitCode = 1;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
if (options.calibrateSpace) {
|
|
1044
|
+
try {
|
|
1045
|
+
await applySpaceCalibration(space, options.deleteUnregistered);
|
|
1046
|
+
}
|
|
1047
|
+
catch (err) {
|
|
1048
|
+
const message = err.message;
|
|
1049
|
+
error(`空间校准失败: ${message}`);
|
|
1050
|
+
debug(`[check] 空间校准异常: sourceSnapshot=${space.snapshotId ?? 'none'}, deleteUnregistered=${options.deleteUnregistered}, error=${message}`);
|
|
1051
|
+
process.exitCode = 1;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
async function applySpaceCalibration(sourceSpace, deleteUnregistered) {
|
|
1057
|
+
const registry = getRegistry();
|
|
1058
|
+
await registry.load();
|
|
1059
|
+
const storePath = await store.getStorePath();
|
|
1060
|
+
const service = new SpaceService(registry, storePath);
|
|
1061
|
+
const cleanupExecutor = service.createCleanupExecutor();
|
|
1062
|
+
const recovered = await cleanupExecutor.recoverPending();
|
|
1063
|
+
debug(`[check] 空间校准开始: store=${storePath}, sourceSnapshot=${sourceSpace.snapshotId ?? 'none'}, sourceIssues=${sourceSpace.issues.length}, unregisteredAction=${deleteUnregistered ? 'delete-unreferenced' : 'keep'}, recovered=${recovered}`);
|
|
1064
|
+
if (recovered > 0)
|
|
1065
|
+
info(`恢复了 ${recovered} 个中断清理记录`);
|
|
1066
|
+
const planningProgress = createSpaceProgressRenderer('正在重新核对空间差异');
|
|
1067
|
+
let snapshot;
|
|
1068
|
+
try {
|
|
1069
|
+
snapshot = await service.buildSnapshot('audit', undefined, planningProgress.update);
|
|
1070
|
+
}
|
|
1071
|
+
finally {
|
|
1072
|
+
planningProgress.close();
|
|
1073
|
+
}
|
|
1074
|
+
const plan = service.buildCalibrationPlan(snapshot);
|
|
1075
|
+
const willDelete = deleteUnregistered && plan.unregistered.deletableCount > 0;
|
|
1076
|
+
debug(`[check] 空间校准计划: plan=${plan.id}, snapshot=${snapshot.id}, metadataActions=${plan.actions.length}, changed=${plan.summary.changedCount}, missing=${plan.summary.missingCount}, duplicates=${plan.summary.duplicateSharedCount}, unregistered=${plan.unregistered.totalCount}, deletable=${plan.unregistered.deletableCount}, protected=${plan.unregistered.protectedCount}, willDelete=${willDelete}`);
|
|
1077
|
+
if (plan.actions.length === 0 && !willDelete) {
|
|
1078
|
+
if (plan.unregistered.totalCount > 0) {
|
|
1079
|
+
success(`登记数据无需调整,保留 ${plan.unregistered.totalCount} 项未登记平台`);
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
success('空间登记与 Store 实际状态一致');
|
|
1083
|
+
}
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
const stageProgress = {
|
|
1087
|
+
'validate-plan': createSpaceProgressRenderer('正在验证校准计划'),
|
|
1088
|
+
'verify-result': createSpaceProgressRenderer('正在验证校准结果'),
|
|
1089
|
+
};
|
|
1090
|
+
let calibrationResult;
|
|
1091
|
+
try {
|
|
1092
|
+
calibrationResult = await service.createCalibrationExecutor().execute(plan, {
|
|
1093
|
+
deleteUnregistered,
|
|
1094
|
+
onProgress: (stage, progress) => stageProgress[stage].update(progress),
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
finally {
|
|
1098
|
+
stageProgress['validate-plan'].close();
|
|
1099
|
+
stageProgress['verify-result'].close();
|
|
1100
|
+
}
|
|
1101
|
+
const remainingPlan = service.buildCalibrationPlan(calibrationResult.finalSnapshot);
|
|
1102
|
+
const cleanupFailed = calibrationResult.cleanup != null && calibrationResult.cleanup.status !== 'completed';
|
|
1103
|
+
if (cleanupFailed) {
|
|
1104
|
+
process.exitCode = 1;
|
|
1105
|
+
warn(`空间校准部分完成: 更新 ${calibrationResult.metadataActionCount} 项登记数据,未登记平台删除成功 ${calibrationResult.cleanup.succeeded.length} 项、失败 ${calibrationResult.cleanup.failed.length} 项`);
|
|
1106
|
+
for (const failure of calibrationResult.cleanup.failed) {
|
|
1107
|
+
error(`未登记平台删除失败: ${failure.id.libName}/${failure.id.commit.slice(0, 7)}/${failure.platform ?? 'all'} - ${failure.message}`);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
else {
|
|
1111
|
+
success(`空间校准完成: 更新 ${calibrationResult.metadataActionCount} 项登记数据`);
|
|
1112
|
+
}
|
|
1113
|
+
if (calibrationResult.cleanup && !cleanupFailed) {
|
|
1114
|
+
info(`未登记平台清理: 删除 ${calibrationResult.cleanup.succeeded.length} 项,释放 ${formatSize(calibrationResult.cleanup.released.bytes)}`);
|
|
1115
|
+
}
|
|
1116
|
+
if (remainingPlan.unregistered.totalCount > 0) {
|
|
1117
|
+
info(`保留未登记平台: ${remainingPlan.unregistered.totalCount} 项`);
|
|
1118
|
+
}
|
|
1119
|
+
if (remainingPlan.actions.length > 0) {
|
|
1120
|
+
process.exitCode = 1;
|
|
1121
|
+
warn(`重新检查仍发现 ${remainingPlan.actions.length} 项登记差异,请查看详细日志`);
|
|
1122
|
+
}
|
|
1123
|
+
debug(`[check] 空间校准完成: plan=${plan.id}, finalSnapshot=${calibrationResult.finalSnapshot.id}, metadataActions=${calibrationResult.metadataActionCount}, cleanupStatus=${calibrationResult.cleanup?.status ?? 'skipped'}, cleanupSucceeded=${calibrationResult.cleanup?.succeeded.length ?? 0}, cleanupFailed=${calibrationResult.cleanup?.failed.length ?? 0}, remainingMetadata=${remainingPlan.actions.length}, remainingUnregistered=${remainingPlan.unregistered.totalCount}, finalDifference=${calibrationResult.finalSnapshot.totals.differenceBytes}`);
|
|
786
1124
|
}
|
|
787
1125
|
function commitKey(libName, commit) {
|
|
788
1126
|
return JSON.stringify([libName, commit]);
|
|
@@ -839,6 +1177,7 @@ async function applyAllIssueFixes(issues) {
|
|
|
839
1177
|
const recovered = await executor.recoverPending();
|
|
840
1178
|
debug(`[check] 修复开始: invalidProjects=${issues.invalidProjects.length}, danglingLinks=${issues.danglingLinks.length}, orphanLibraries=${issues.orphanLibraries.length}, staleReferences=${issues.staleReferences.length}, corruptedStores=${issues.corruptedStores.length}, recovered=${recovered}`);
|
|
841
1179
|
let fixed = 0;
|
|
1180
|
+
let failed = 0;
|
|
842
1181
|
// 1. 清理无效项目
|
|
843
1182
|
for (const p of issues.invalidProjects) {
|
|
844
1183
|
try {
|
|
@@ -848,6 +1187,7 @@ async function applyAllIssueFixes(issues) {
|
|
|
848
1187
|
}
|
|
849
1188
|
catch (err) {
|
|
850
1189
|
error(`[err] 清理项目失败: ${p.path} - ${err.message}`);
|
|
1190
|
+
failed++;
|
|
851
1191
|
}
|
|
852
1192
|
}
|
|
853
1193
|
// 2. 移除悬挂链接
|
|
@@ -856,7 +1196,11 @@ async function applyAllIssueFixes(issues) {
|
|
|
856
1196
|
await fs.unlink(link.path);
|
|
857
1197
|
const project = registry.getProject(link.projectHash);
|
|
858
1198
|
if (project) {
|
|
859
|
-
project.dependencies = project.dependencies.filter((
|
|
1199
|
+
project.dependencies = project.dependencies.filter((dependency) => !(dependency.libName === link.dep.libName &&
|
|
1200
|
+
dependency.commit === link.dep.commit &&
|
|
1201
|
+
dependency.platform === link.dep.platform &&
|
|
1202
|
+
dependency.linkedPath === link.dep.linkedPath &&
|
|
1203
|
+
(dependency.scope ?? '') === (link.dep.scope ?? '')));
|
|
860
1204
|
registry.updateProject(link.projectHash, {
|
|
861
1205
|
dependencies: project.dependencies,
|
|
862
1206
|
});
|
|
@@ -866,6 +1210,7 @@ async function applyAllIssueFixes(issues) {
|
|
|
866
1210
|
}
|
|
867
1211
|
catch (err) {
|
|
868
1212
|
error(`[err] 移除链接失败: ${link.path} - ${err.message}`);
|
|
1213
|
+
failed++;
|
|
869
1214
|
}
|
|
870
1215
|
}
|
|
871
1216
|
// 3. 删除孤立库。显式修复允许清理该版本中的未登记内容。
|
|
@@ -886,9 +1231,11 @@ async function applyAllIssueFixes(issues) {
|
|
|
886
1231
|
for (const failure of result?.failed ?? []) {
|
|
887
1232
|
error(`[err] 删除孤立库失败: ${failure.id.libName}/${failure.id.commit.slice(0, 7)} - ${failure.message}`);
|
|
888
1233
|
}
|
|
1234
|
+
failed += result?.failed.length ?? (orphanSelections.length > 0 ? 1 : 0);
|
|
889
1235
|
}
|
|
890
1236
|
catch (err) {
|
|
891
1237
|
error(`[err] 删除孤立库失败: ${err.message}`);
|
|
1238
|
+
failed++;
|
|
892
1239
|
}
|
|
893
1240
|
// 4. 移除失效引用
|
|
894
1241
|
for (const ref of issues.staleReferences) {
|
|
@@ -898,6 +1245,7 @@ async function applyAllIssueFixes(issues) {
|
|
|
898
1245
|
const colonIndex = ref.libKey.lastIndexOf(':');
|
|
899
1246
|
if (colonIndex === -1) {
|
|
900
1247
|
error(`[err] 无效的 libKey: ${ref.libKey}`);
|
|
1248
|
+
failed++;
|
|
901
1249
|
continue;
|
|
902
1250
|
}
|
|
903
1251
|
const libName = ref.libKey.slice(0, colonIndex);
|
|
@@ -911,6 +1259,7 @@ async function applyAllIssueFixes(issues) {
|
|
|
911
1259
|
}
|
|
912
1260
|
catch (err) {
|
|
913
1261
|
error(`[err] 移除引用失败: ${ref.libKey} - ${err.message}`);
|
|
1262
|
+
failed++;
|
|
914
1263
|
}
|
|
915
1264
|
}
|
|
916
1265
|
// 5. 清理损坏的 Store 条目。共享冲突整版本处理,其他问题按平台处理。
|
|
@@ -948,16 +1297,24 @@ async function applyAllIssueFixes(issues) {
|
|
|
948
1297
|
for (const failure of result?.failed ?? []) {
|
|
949
1298
|
error(`清理损坏 Store 失败: ${failure.id.libName}/${failure.id.commit.slice(0, 7)}/${failure.platform ?? 'all'} - ${failure.message}`);
|
|
950
1299
|
}
|
|
1300
|
+
failed += result?.failed.length ?? (cleanup.selections.length > 0 ? 1 : 0);
|
|
951
1301
|
}
|
|
952
1302
|
catch (err) {
|
|
953
1303
|
error(`清理损坏 Store 失败: category=${cleanup.category} - ${err.message}`);
|
|
1304
|
+
failed++;
|
|
954
1305
|
}
|
|
955
1306
|
}
|
|
956
1307
|
await registry.save();
|
|
957
1308
|
blank();
|
|
958
1309
|
separator();
|
|
959
|
-
|
|
960
|
-
|
|
1310
|
+
if (failed > 0) {
|
|
1311
|
+
warn(`修复部分完成: 解决 ${fixed} 个问题,失败 ${failed} 项操作`);
|
|
1312
|
+
}
|
|
1313
|
+
else {
|
|
1314
|
+
success(`修复完成: ${fixed} 个问题已解决`);
|
|
1315
|
+
}
|
|
1316
|
+
debug(`[check] 修复完成: fixed=${fixed}, failed=${failed}`);
|
|
1317
|
+
return { fixed, failed };
|
|
961
1318
|
}
|
|
962
1319
|
// ============ 工具函数 ============
|
|
963
1320
|
async function getDirSizeRecursive(dirPath) {
|
|
@@ -1005,6 +1362,12 @@ export async function verifyIntegrity() {
|
|
|
1005
1362
|
export async function collectCheckResultForTest(options) {
|
|
1006
1363
|
return collectAllIssues(options);
|
|
1007
1364
|
}
|
|
1365
|
+
export async function runCheckForTest(options) {
|
|
1366
|
+
await runCheck(options);
|
|
1367
|
+
}
|
|
1368
|
+
export async function interactiveCheckForTest(result, options) {
|
|
1369
|
+
await interactiveCheck(result, options);
|
|
1370
|
+
}
|
|
1008
1371
|
/**
|
|
1009
1372
|
* 修复问题(兼容旧 repair 命令)
|
|
1010
1373
|
*/
|
|
@@ -1012,16 +1375,22 @@ export async function repairIssues(options) {
|
|
|
1012
1375
|
const result = await collectAllIssues();
|
|
1013
1376
|
// 渲染报告
|
|
1014
1377
|
renderReport(result);
|
|
1015
|
-
const hasIssues = result.summary.integrityIssues > 0;
|
|
1378
|
+
const hasIssues = result.summary.integrityIssues > 0 || hasSpaceCalibrationChoice(result.space);
|
|
1016
1379
|
if (!hasIssues) {
|
|
1017
1380
|
return;
|
|
1018
1381
|
}
|
|
1019
1382
|
if (options.dryRun) {
|
|
1020
1383
|
blank();
|
|
1021
|
-
hint('运行 td check --fix
|
|
1384
|
+
hint('运行 td check --fix 修复可修复问题');
|
|
1022
1385
|
return;
|
|
1023
1386
|
}
|
|
1024
|
-
await fixAllIssues(result.integrity,
|
|
1387
|
+
await fixAllIssues(result.integrity, result.space, {
|
|
1388
|
+
force: options.force,
|
|
1389
|
+
calibrateSpace: hasSpaceCalibrationChoice(result.space),
|
|
1390
|
+
deleteUnregistered: false,
|
|
1391
|
+
verifyStoreIntegrity: false,
|
|
1392
|
+
});
|
|
1393
|
+
renderUncachedDependencyHint(result.integrity);
|
|
1025
1394
|
}
|
|
1026
1395
|
/**
|
|
1027
1396
|
* 校验所有 Store 条目的文件完整性
|