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/link.js
CHANGED
|
@@ -21,6 +21,7 @@ import { ProgressTracker, DownloadMonitor, MultiBarManager } from '../utils/prog
|
|
|
21
21
|
import { success, warn, error, info, hint, blank, separator, debug } from '../utils/logger.js';
|
|
22
22
|
import { verifyLocalCommit, findSubmoduleConfigs } from '../utils/git.js';
|
|
23
23
|
import { DependencyStatus } from '../types/index.js';
|
|
24
|
+
import { SpaceService, buildHalfCleanupSelectionSummary, buildUnreferencedSelectionSummary, estimateDownloadSpace, } from '../core/space/service.js';
|
|
24
25
|
import { withGlobalLock } from '../utils/global-lock.js';
|
|
25
26
|
import { selectPlatforms, parsePlatformArgs, selectOption, selectOptionalConfigs, PROMPT_CANCELLED } from '../utils/prompt.js';
|
|
26
27
|
import pLimit from 'p-limit';
|
|
@@ -75,6 +76,100 @@ function resolveRequestedPlatformsByActual(requestedPlatforms, sparse, actualPla
|
|
|
75
76
|
missingRequested: requestedPlatforms.filter((platform) => !satisfiedRequested.includes(platform)),
|
|
76
77
|
};
|
|
77
78
|
}
|
|
79
|
+
async function verifyGeneralStoreState(dependency) {
|
|
80
|
+
const result = await verifyExistingPlatforms(dependency.libName, dependency.commit, [GENERAL_PLATFORM]);
|
|
81
|
+
return {
|
|
82
|
+
valid: result.valid.length > 0,
|
|
83
|
+
repairRequired: result.repairRequired.length > 0,
|
|
84
|
+
corrupted: result.corrupted.length > 0,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
async function executeLinkStoreRepair(dependency, platforms, options) {
|
|
88
|
+
const registry = getRegistry();
|
|
89
|
+
const storePath = await store.getStorePath();
|
|
90
|
+
const spaceService = new SpaceService(registry, storePath);
|
|
91
|
+
const plan = await spaceService.buildCleanupPlan({
|
|
92
|
+
reason: 'repair',
|
|
93
|
+
selections: [{
|
|
94
|
+
id: { libName: dependency.libName, commit: dependency.commit },
|
|
95
|
+
platforms,
|
|
96
|
+
includeShared: options.includeShared,
|
|
97
|
+
}],
|
|
98
|
+
requireUnreferenced: false,
|
|
99
|
+
allowUnknownContent: false,
|
|
100
|
+
});
|
|
101
|
+
for (const warningMessage of plan.warnings)
|
|
102
|
+
warn(`[space] ${warningMessage}`);
|
|
103
|
+
debug(`[link] 调用统一清理执行器: category=${options.category}, plan=${plan.id}, lib=${dependency.libName}, commit=${dependency.commit}, platforms=${platforms === 'all' ? 'all' : platforms.join(',')}, includeShared=${Boolean(options.includeShared)}, groups=${plan.groups.length}, expected=${plan.expectedRelease.bytes}`);
|
|
104
|
+
if (plan.groups.length === 0)
|
|
105
|
+
return;
|
|
106
|
+
const result = await spaceService.createCleanupExecutor().execute(plan);
|
|
107
|
+
debug(`[link] 清理执行返回: category=${options.category}, plan=${plan.id}, status=${result.status}, succeeded=${result.succeeded.length}, failed=${result.failed.length}, released=${result.released.bytes}`);
|
|
108
|
+
if (result.status !== 'completed') {
|
|
109
|
+
throw new Error(`${dependency.libName}@${dependency.commit.slice(0, 8)} 缓存修复未完整完成: ${result.failed.map((item) => item.message).join('; ')}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function prepareGeneralStoreRepair(dependency, repairRequiredPlatforms) {
|
|
113
|
+
const generalState = await verifyGeneralStoreState(dependency);
|
|
114
|
+
if (!generalState.valid && (generalState.corrupted || generalState.repairRequired)) {
|
|
115
|
+
await executeLinkStoreRepair(dependency, 'all', { category: 'general-corruption' });
|
|
116
|
+
if (!repairRequiredPlatforms.includes(GENERAL_PLATFORM)) {
|
|
117
|
+
repairRequiredPlatforms.push(GENERAL_PLATFORM);
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
async function prepareSharedStoreRepair(dependency, requestedPlatforms, repairRequiredPlatforms) {
|
|
124
|
+
const storePath = await config.getStorePath();
|
|
125
|
+
if (!storePath)
|
|
126
|
+
return [];
|
|
127
|
+
const sharedPath = path.join(storePath, dependency.libName, dependency.commit, '_shared');
|
|
128
|
+
try {
|
|
129
|
+
await fs.access(sharedPath);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
const result = await verifyExistingPlatforms(dependency.libName, dependency.commit, [SHARED_PLATFORM]);
|
|
135
|
+
if (result.valid.length > 0 && result.corrupted.length === 0 && result.repairRequired.length === 0) {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
const removedPlatforms = [];
|
|
139
|
+
for (const platform of requestedPlatforms) {
|
|
140
|
+
if (!repairRequiredPlatforms.includes(platform)) {
|
|
141
|
+
repairRequiredPlatforms.push(platform);
|
|
142
|
+
}
|
|
143
|
+
removedPlatforms.push(platform);
|
|
144
|
+
}
|
|
145
|
+
await executeLinkStoreRepair(dependency, 'all', {
|
|
146
|
+
includeShared: true,
|
|
147
|
+
category: 'shared-corruption',
|
|
148
|
+
});
|
|
149
|
+
debug(`${dependency.libName} _shared 本地缓存不完整,准备随平台 [${requestedPlatforms.join(', ') || '无'}] 重新下载`);
|
|
150
|
+
return removedPlatforms;
|
|
151
|
+
}
|
|
152
|
+
async function hasRequestedPlatformContent(dependency, requestedPlatforms, vars) {
|
|
153
|
+
if (requestedPlatforms.length === 0)
|
|
154
|
+
return false;
|
|
155
|
+
const requestedTargets = [...new Set(requestedPlatforms.flatMap((platform) => getRequestedPlatformTargets(platform, dependency.sparse, vars)))];
|
|
156
|
+
const storePath = await store.getStorePath();
|
|
157
|
+
const commitPath = path.join(storePath, dependency.libName, dependency.commit);
|
|
158
|
+
try {
|
|
159
|
+
const entries = await fs.readdir(commitPath, { withFileTypes: true });
|
|
160
|
+
for (const entry of entries) {
|
|
161
|
+
if (!entry.isDirectory() || !requestedTargets.includes(entry.name))
|
|
162
|
+
continue;
|
|
163
|
+
if (await store.exists(dependency.libName, dependency.commit, entry.name)) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// Store 目录不存在时继续使用历史记录或后续 General 检测
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
78
173
|
export function assessDownloadResult(requestedPlatforms, sparse, downloadResult, vars) {
|
|
79
174
|
const downloadedActual = new Set(downloadResult.platformDirs);
|
|
80
175
|
const { satisfiedRequested, missingRequested, } = resolveRequestedPlatformsByActual(requestedPlatforms, sparse, downloadResult.platformDirs, vars);
|
|
@@ -92,8 +187,9 @@ export function assessDownloadResult(requestedPlatforms, sparse, downloadResult,
|
|
|
92
187
|
isPureGeneral,
|
|
93
188
|
};
|
|
94
189
|
}
|
|
95
|
-
async function resolveRequestedStoreState(dependency, requestedPlatforms, vars) {
|
|
190
|
+
async function resolveRequestedStoreState(dependency, requestedPlatforms, vars, repairMissingRecords = true) {
|
|
96
191
|
const resolvedExisting = [];
|
|
192
|
+
const stalePlatforms = [];
|
|
97
193
|
const candidatePlatforms = [...new Set(requestedPlatforms.flatMap((platform) => getRequestedPlatformTargets(platform, dependency.sparse, vars)))];
|
|
98
194
|
for (const platform of candidatePlatforms) {
|
|
99
195
|
if (await store.exists(dependency.libName, dependency.commit, platform)) {
|
|
@@ -103,13 +199,13 @@ async function resolveRequestedStoreState(dependency, requestedPlatforms, vars)
|
|
|
103
199
|
const registry = getRegistry();
|
|
104
200
|
const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
|
|
105
201
|
if (registry.getStore(storeKey)) {
|
|
106
|
-
|
|
107
|
-
const storePath = await store.getStorePath();
|
|
108
|
-
const platformPath = store.getLibraryPath(storePath, dependency.libName, dependency.commit, platform);
|
|
109
|
-
await fs.rm(platformPath, { recursive: true, force: true }).catch(() => { });
|
|
202
|
+
stalePlatforms.push(platform);
|
|
110
203
|
}
|
|
111
204
|
}
|
|
112
205
|
}
|
|
206
|
+
if (repairMissingRecords && stalePlatforms.length > 0) {
|
|
207
|
+
await executeLinkStoreRepair(dependency, stalePlatforms, { category: 'missing-platform-path' });
|
|
208
|
+
}
|
|
113
209
|
const result = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, resolvedExisting, vars);
|
|
114
210
|
debug(`[link-store-state] ${dependency.libName}@${dependency.commit.slice(0, 7)} 请求平台=${requestedPlatforms.join(', ') || '空'}, Store候选=${candidatePlatforms.join(', ') || '空'}, 实际存在=${result.actualExisting.join(', ') || '无'}, 满足请求=${result.satisfiedRequested.join(', ') || '无'}, 缺失请求=${result.missingRequested.join(', ') || '无'}`);
|
|
115
211
|
return result;
|
|
@@ -170,7 +266,7 @@ function upsertLibraryAvailability(dependency, updates) {
|
|
|
170
266
|
lastAccess: new Date().toISOString(),
|
|
171
267
|
});
|
|
172
268
|
}
|
|
173
|
-
export async function resolveLibraryGeneralState(dependency, requestedPlatforms = [], vars) {
|
|
269
|
+
export async function resolveLibraryGeneralState(dependency, requestedPlatforms = [], vars, updateRegistry = true) {
|
|
174
270
|
const registry = getRegistry();
|
|
175
271
|
const libKey = registry.getLibraryKey(dependency.libName, dependency.commit);
|
|
176
272
|
const library = registry.getLibrary(libKey);
|
|
@@ -182,9 +278,9 @@ export async function resolveLibraryGeneralState(dependency, requestedPlatforms
|
|
|
182
278
|
&& library?.isGeneral === false
|
|
183
279
|
&& library.platforms.some((platform) => platform !== GENERAL_PLATFORM);
|
|
184
280
|
if (requestedPlatforms.length > 0) {
|
|
185
|
-
const requestedState = await resolveRequestedStoreState(dependency, requestedPlatforms, vars);
|
|
281
|
+
const requestedState = await resolveRequestedStoreState(dependency, requestedPlatforms, vars, updateRegistry);
|
|
186
282
|
if (requestedState.actualExisting.length > 0) {
|
|
187
|
-
if (library?.isGeneral === true) {
|
|
283
|
+
if (updateRegistry && library?.isGeneral === true) {
|
|
188
284
|
registry.updateLibrary(libKey, {
|
|
189
285
|
platforms: requestedState.actualExisting,
|
|
190
286
|
isGeneral: false,
|
|
@@ -195,30 +291,12 @@ export async function resolveLibraryGeneralState(dependency, requestedPlatforms
|
|
|
195
291
|
}
|
|
196
292
|
}
|
|
197
293
|
if (requestedPlatforms.length > 0 && library?.isGeneral === true) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const commitPath = path.join(storePath, dependency.libName, dependency.commit);
|
|
201
|
-
try {
|
|
202
|
-
const entries = await fs.readdir(commitPath, { withFileTypes: true });
|
|
203
|
-
let hasRequestedPlatformDir = false;
|
|
204
|
-
for (const entry of entries) {
|
|
205
|
-
if (!entry.isDirectory() || !requestedTargets.includes(entry.name))
|
|
206
|
-
continue;
|
|
207
|
-
if (await store.exists(dependency.libName, dependency.commit, entry.name)) {
|
|
208
|
-
hasRequestedPlatformDir = true;
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
if (hasRequestedPlatformDir) {
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
catch {
|
|
217
|
-
// Store 目录不存在时继续使用历史记录或后续 General 检测
|
|
294
|
+
if (await hasRequestedPlatformContent(dependency, requestedPlatforms, vars)) {
|
|
295
|
+
return false;
|
|
218
296
|
}
|
|
219
297
|
}
|
|
220
298
|
if (requestedPlatforms.length > 0 && hasPlatformSparse) {
|
|
221
|
-
if (library?.isGeneral === true) {
|
|
299
|
+
if (updateRegistry && library?.isGeneral === true) {
|
|
222
300
|
registry.updateLibrary(libKey, {
|
|
223
301
|
isGeneral: false,
|
|
224
302
|
lastAccess: new Date().toISOString(),
|
|
@@ -231,16 +309,21 @@ export async function resolveLibraryGeneralState(dependency, requestedPlatforms
|
|
|
231
309
|
}
|
|
232
310
|
if (typeof library?.isGeneral === 'boolean') {
|
|
233
311
|
if (library.isGeneral) {
|
|
234
|
-
const
|
|
235
|
-
if (!
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
312
|
+
const generalState = await verifyGeneralStoreState(dependency);
|
|
313
|
+
if (!generalState.valid) {
|
|
314
|
+
if (updateRegistry && (generalState.corrupted || generalState.repairRequired)) {
|
|
315
|
+
await executeLinkStoreRepair(dependency, 'all', { category: 'general-state-repair' });
|
|
316
|
+
}
|
|
317
|
+
if (updateRegistry) {
|
|
318
|
+
registry.updateLibrary(libKey, {
|
|
319
|
+
isGeneral: false,
|
|
320
|
+
lastAccess: new Date().toISOString(),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
240
323
|
return false;
|
|
241
324
|
}
|
|
242
325
|
const actualGeneral = await store.isGeneralLib(dependency.libName, dependency.commit);
|
|
243
|
-
if (!actualGeneral) {
|
|
326
|
+
if (updateRegistry && !actualGeneral) {
|
|
244
327
|
registry.updateLibrary(libKey, {
|
|
245
328
|
isGeneral: false,
|
|
246
329
|
lastAccess: new Date().toISOString(),
|
|
@@ -249,7 +332,7 @@ export async function resolveLibraryGeneralState(dependency, requestedPlatforms
|
|
|
249
332
|
return actualGeneral;
|
|
250
333
|
}
|
|
251
334
|
const actualGeneral = await store.isGeneralLib(dependency.libName, dependency.commit);
|
|
252
|
-
if (actualGeneral) {
|
|
335
|
+
if (updateRegistry && actualGeneral) {
|
|
253
336
|
registry.updateLibrary(libKey, {
|
|
254
337
|
platforms: [GENERAL_PLATFORM],
|
|
255
338
|
isGeneral: true,
|
|
@@ -463,7 +546,7 @@ async function linkScope(params) {
|
|
|
463
546
|
blank();
|
|
464
547
|
// 2. 分类依赖
|
|
465
548
|
const scopePath = path.dirname(path.dirname(configPath));
|
|
466
|
-
const classified = await classifyDependencies(dependencies, scopePath, configPath, platforms, configVars);
|
|
549
|
+
const classified = await classifyDependencies(dependencies, scopePath, configPath, platforms, configVars, !dryRun);
|
|
467
550
|
const stats = {
|
|
468
551
|
linked: 0, relink: 0, replace: 0, absorb: 0, missing: 0, linkNew: 0,
|
|
469
552
|
};
|
|
@@ -480,14 +563,31 @@ async function linkScope(params) {
|
|
|
480
563
|
}
|
|
481
564
|
// 4. 磁盘空间预检
|
|
482
565
|
if (stats.missing > 0 && download) {
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
566
|
+
const downloadRequests = classified
|
|
567
|
+
.filter((item) => item.status === DependencyStatus.MISSING)
|
|
568
|
+
.map((item) => ({
|
|
569
|
+
libName: item.dependency.libName,
|
|
570
|
+
commit: item.dependency.commit,
|
|
571
|
+
platforms: platforms.filter((platform) => !getBlockedRequestedPlatforms(registry, item.dependency, platforms, configVars).includes(platform)),
|
|
572
|
+
}))
|
|
573
|
+
.filter((request) => request.platforms.length > 0);
|
|
574
|
+
const estimate = estimateDownloadSpace(registry, downloadRequests);
|
|
575
|
+
for (const evidence of estimate.evidence)
|
|
576
|
+
debug(`[space] 下载容量依据: ${evidence}`);
|
|
577
|
+
if (estimate.status === 'unknown' || !estimate.amount) {
|
|
578
|
+
warn(`下载容量未知: ${estimate.warnings.join('; ')}`);
|
|
579
|
+
debug(`[space] 下载容量预检跳过: requests=${downloadRequests.length}, status=${estimate.status}, warnings=${estimate.warnings.length}`);
|
|
488
580
|
}
|
|
489
|
-
if (
|
|
490
|
-
|
|
581
|
+
else if (downloadRequests.length > 0) {
|
|
582
|
+
const spaceCheck = await checkDiskSpace(storePath, estimate.amount.bytes);
|
|
583
|
+
debug(`[space] 下载容量预检: requests=${downloadRequests.length}, estimated=${estimate.amount.bytes}, available=${spaceCheck.available}, required=${spaceCheck.required}, sufficient=${spaceCheck.sufficient}`);
|
|
584
|
+
if (!spaceCheck.sufficient) {
|
|
585
|
+
error(`磁盘空间不足: 预计需要 ${formatSize(spaceCheck.required)},可用 ${formatSize(spaceCheck.available)}(含 1GB 安全余量)`);
|
|
586
|
+
process.exit(EXIT_CODES.IOERR);
|
|
587
|
+
}
|
|
588
|
+
if (spaceCheck.available === 0) {
|
|
589
|
+
warn('无法获取磁盘空间信息,继续执行');
|
|
590
|
+
}
|
|
491
591
|
}
|
|
492
592
|
}
|
|
493
593
|
// 5. 预扫描额外平台(仅主项目 scope)
|
|
@@ -601,7 +701,7 @@ async function linkScope(params) {
|
|
|
601
701
|
}
|
|
602
702
|
// 7. 逐个处理依赖(LINKED/RELINK/REPLACE/ABSORB/LINK_NEW)
|
|
603
703
|
for (const item of classified) {
|
|
604
|
-
const { dependency, status, localPath } = item;
|
|
704
|
+
const { dependency, status, localPath, repairRequiredPlatforms } = item;
|
|
605
705
|
await store.ensureCompatibleStore(storePath, dependency.libName, dependency.commit);
|
|
606
706
|
switch (status) {
|
|
607
707
|
case DependencyStatus.LINKED: {
|
|
@@ -612,6 +712,7 @@ async function linkScope(params) {
|
|
|
612
712
|
const supplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, {
|
|
613
713
|
vars: configVars,
|
|
614
714
|
useGitLightweightDownload: gitLightweightDownload,
|
|
715
|
+
repairRequiredPlatforms,
|
|
615
716
|
});
|
|
616
717
|
await registerNestedLibraries(supplementResult.nestedLibraries, projectHash);
|
|
617
718
|
if (supplementResult.downloaded.length > 0) {
|
|
@@ -648,6 +749,7 @@ async function linkScope(params) {
|
|
|
648
749
|
const relinkSupplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, {
|
|
649
750
|
vars: configVars,
|
|
650
751
|
useGitLightweightDownload: gitLightweightDownload,
|
|
752
|
+
repairRequiredPlatforms,
|
|
651
753
|
});
|
|
652
754
|
await registerNestedLibraries(relinkSupplementResult.nestedLibraries, projectHash);
|
|
653
755
|
const { actualExisting: relinkExisting, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
|
|
@@ -705,6 +807,7 @@ async function linkScope(params) {
|
|
|
705
807
|
const replaceSupplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, {
|
|
706
808
|
vars: configVars,
|
|
707
809
|
useGitLightweightDownload: gitLightweightDownload,
|
|
810
|
+
repairRequiredPlatforms,
|
|
708
811
|
});
|
|
709
812
|
await registerNestedLibraries(replaceSupplementResult.nestedLibraries, projectHash);
|
|
710
813
|
const { actualExisting: replaceExisting, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
|
|
@@ -814,6 +917,7 @@ async function linkScope(params) {
|
|
|
814
917
|
const absorbSupplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, {
|
|
815
918
|
vars: configVars,
|
|
816
919
|
useGitLightweightDownload: gitLightweightDownload,
|
|
920
|
+
repairRequiredPlatforms,
|
|
817
921
|
});
|
|
818
922
|
await registerNestedLibraries(absorbSupplementResult.nestedLibraries, projectHash);
|
|
819
923
|
if (absorbSupplementResult.downloaded.length > 0) {
|
|
@@ -880,9 +984,20 @@ async function linkScope(params) {
|
|
|
880
984
|
const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
|
|
881
985
|
const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
|
|
882
986
|
const { missingRequested: missing } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
|
|
987
|
+
const repairRequiredSet = new Set(repairRequiredPlatforms ?? []);
|
|
988
|
+
const repairDownloadTargets = missing.filter((platform) => repairRequiredSet.has(platform));
|
|
989
|
+
const isRepairingGeneral = repairRequiredSet.has(GENERAL_PLATFORM);
|
|
883
990
|
const linkNewLibId = `${dependency.libName}@${dependency.commit}`;
|
|
884
991
|
if (missing.length > 0 && !skipAllDownloads && downloadConfirmedLibs.has(linkNewLibId)) {
|
|
885
|
-
|
|
992
|
+
if (repairDownloadTargets.length > 0 || isRepairingGeneral) {
|
|
993
|
+
const repairTargetText = repairDownloadTargets.length > 0
|
|
994
|
+
? repairDownloadTargets.join(', ')
|
|
995
|
+
: GENERAL_PLATFORM;
|
|
996
|
+
debug(`${dependency.libName} ${repairTargetText} 本地缓存不完整,开始重新下载`);
|
|
997
|
+
}
|
|
998
|
+
else {
|
|
999
|
+
info(`${dependency.libName} 缺少平台 [${missing.join(', ')}],开始下载...`);
|
|
1000
|
+
}
|
|
886
1001
|
const linkNewLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
|
|
887
1002
|
const linkNewHistoryLib = registry.getLibrary(linkNewLibKey);
|
|
888
1003
|
const linkNewMonitor = new DownloadMonitor({
|
|
@@ -898,6 +1013,17 @@ async function linkScope(params) {
|
|
|
898
1013
|
onHeartbeat: (message) => { linkNewMonitor.heartbeat(message); },
|
|
899
1014
|
});
|
|
900
1015
|
}
|
|
1016
|
+
catch (err) {
|
|
1017
|
+
if (repairDownloadTargets.length > 0 || isRepairingGeneral) {
|
|
1018
|
+
const repairTargetText = repairDownloadTargets.length > 0
|
|
1019
|
+
? repairDownloadTargets.join(', ')
|
|
1020
|
+
: GENERAL_PLATFORM;
|
|
1021
|
+
error(`${dependency.libName} ${repairTargetText} 本地缓存不完整,自动重新下载失败`);
|
|
1022
|
+
hint(`可运行 td reset ${dependency.libName} 后重试`);
|
|
1023
|
+
debug(`${dependency.libName} 下载失败: ${err.message}`);
|
|
1024
|
+
}
|
|
1025
|
+
throw err;
|
|
1026
|
+
}
|
|
901
1027
|
finally {
|
|
902
1028
|
await linkNewMonitor.stop();
|
|
903
1029
|
}
|
|
@@ -912,6 +1038,10 @@ async function linkScope(params) {
|
|
|
912
1038
|
tx.recordOp('absorb', linkNewCommitPath, downloadResult.libDir);
|
|
913
1039
|
const linkNewAbsorbResult = await store.absorbLib(downloadResult.libDir, filteredDownloaded, dependency.libName, dependency.commit);
|
|
914
1040
|
await registerNestedLibraries(linkNewAbsorbResult.nestedLibraries, projectHash);
|
|
1041
|
+
const repaired = filteredDownloaded.filter((platform) => repairRequiredSet.has(platform));
|
|
1042
|
+
if (repaired.length > 0) {
|
|
1043
|
+
warn(`${dependency.libName} ${repaired.join(', ')} 本地缓存不完整,已重新下载`);
|
|
1044
|
+
}
|
|
915
1045
|
}
|
|
916
1046
|
}
|
|
917
1047
|
finally {
|
|
@@ -927,7 +1057,12 @@ async function linkScope(params) {
|
|
|
927
1057
|
await linker.linkGeneral(localPath, sharedPath);
|
|
928
1058
|
await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
|
|
929
1059
|
generalLibs.add(dependency.libName);
|
|
930
|
-
|
|
1060
|
+
if (isRepairingGeneral) {
|
|
1061
|
+
warn(`${dependency.libName} ${GENERAL_PLATFORM} 本地缓存不完整,已重新下载`);
|
|
1062
|
+
}
|
|
1063
|
+
else {
|
|
1064
|
+
success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,创建链接`);
|
|
1065
|
+
}
|
|
931
1066
|
}
|
|
932
1067
|
else if (linkNewExisting.length === 0) {
|
|
933
1068
|
const { KNOWN_PLATFORM_VALUES } = await import('../core/platform.js');
|
|
@@ -977,13 +1112,14 @@ async function linkScope(params) {
|
|
|
977
1112
|
const multiBarManager = isTTY ? new MultiBarManager() : null;
|
|
978
1113
|
const downloadLimit = pLimit(concurrency);
|
|
979
1114
|
const downloadTasks = toDownload.map((item) => downloadLimit(async () => {
|
|
980
|
-
const { dependency, localPath } = item;
|
|
1115
|
+
const { dependency, localPath, repairRequiredPlatforms = [] } = item;
|
|
1116
|
+
const repairRequiredSet = new Set(repairRequiredPlatforms);
|
|
981
1117
|
const pLog = {
|
|
982
1118
|
info: (msg) => multiBarManager ? multiBarManager.log(`[info] ${msg}`) : info(msg),
|
|
983
1119
|
success: (msg) => multiBarManager ? multiBarManager.log(`[ok] ${msg}`) : success(msg),
|
|
984
1120
|
hint: (msg) => multiBarManager ? multiBarManager.log(`[hint] ${msg}`) : hint(msg),
|
|
985
1121
|
warn: (msg) => multiBarManager ? multiBarManager.log(`[warn] ${msg}`) : warn(msg),
|
|
986
|
-
error: (msg) => multiBarManager ? multiBarManager.log(`[
|
|
1122
|
+
error: (msg) => multiBarManager ? multiBarManager.log(`[err] ${msg}`) : error(msg),
|
|
987
1123
|
};
|
|
988
1124
|
try {
|
|
989
1125
|
const storeCommitPath = path.join(storePath, dependency.libName, dependency.commit);
|
|
@@ -1038,7 +1174,17 @@ async function linkScope(params) {
|
|
|
1038
1174
|
}
|
|
1039
1175
|
return { success: false, name: dependency.libName, skipped: true, skippedPlatforms: missing, unsupported: true };
|
|
1040
1176
|
}
|
|
1041
|
-
|
|
1177
|
+
const repairDownloadTargets = dlToDownload.filter((platform) => repairRequiredSet.has(platform));
|
|
1178
|
+
const isRepairingGeneral = repairRequiredSet.has(GENERAL_PLATFORM);
|
|
1179
|
+
if (repairDownloadTargets.length === 0 && !isRepairingGeneral) {
|
|
1180
|
+
pLog.info(`下载 ${dependency.libName} [${dlToDownload.join(', ')}]...`);
|
|
1181
|
+
}
|
|
1182
|
+
else {
|
|
1183
|
+
const repairTargetText = repairDownloadTargets.length > 0
|
|
1184
|
+
? repairDownloadTargets.join(', ')
|
|
1185
|
+
: GENERAL_PLATFORM;
|
|
1186
|
+
debug(`${dependency.libName} ${repairTargetText} 本地缓存不完整,开始重新下载`);
|
|
1187
|
+
}
|
|
1042
1188
|
const estimatedSize = historyLib?.size;
|
|
1043
1189
|
const downloadMonitor = new DownloadMonitor({
|
|
1044
1190
|
name: ` ${dependency.libName}`, estimatedSize, getDirSize,
|
|
@@ -1076,7 +1222,15 @@ async function linkScope(params) {
|
|
|
1076
1222
|
await linker.linkGeneral(localPath, sharedPath);
|
|
1077
1223
|
await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
|
|
1078
1224
|
generalLibs.add(dependency.libName);
|
|
1079
|
-
|
|
1225
|
+
if (isRepairingGeneral) {
|
|
1226
|
+
pLog.warn(`${dependency.libName} ${GENERAL_PLATFORM} 本地缓存不完整,已重新下载`);
|
|
1227
|
+
}
|
|
1228
|
+
else if (repairDownloadTargets.length > 0) {
|
|
1229
|
+
pLog.warn(`${dependency.libName} ${repairDownloadTargets.join(', ')} 本地缓存不完整,已重新下载`);
|
|
1230
|
+
}
|
|
1231
|
+
else {
|
|
1232
|
+
pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,下载完成`);
|
|
1233
|
+
}
|
|
1080
1234
|
return { success: true, name: dependency.libName, downloadedPlatforms: [GENERAL_PLATFORM], skippedPlatforms: [], isGeneral: true };
|
|
1081
1235
|
}
|
|
1082
1236
|
const filteredDownloaded = assessment.downloadedRequested;
|
|
@@ -1111,7 +1265,13 @@ async function linkScope(params) {
|
|
|
1111
1265
|
await linker.linkLib(localPath, storeCommitPath, linkPlatforms);
|
|
1112
1266
|
await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, linkPlatforms, projectHash, false);
|
|
1113
1267
|
const notLinkedPlatforms = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, linkPlatforms, configVars).missingRequested;
|
|
1114
|
-
|
|
1268
|
+
const repaired = filteredDownloaded.filter((platform) => repairRequiredSet.has(platform));
|
|
1269
|
+
if (repaired.length > 0) {
|
|
1270
|
+
pLog.warn(`${dependency.libName} ${repaired.join(', ')} 本地缓存不完整,已重新下载`);
|
|
1271
|
+
}
|
|
1272
|
+
else {
|
|
1273
|
+
pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 下载完成 [${linkPlatforms.join(', ')}]`);
|
|
1274
|
+
}
|
|
1115
1275
|
return { success: true, name: dependency.libName, downloadedPlatforms: linkPlatforms, skippedPlatforms: notLinkedPlatforms };
|
|
1116
1276
|
}
|
|
1117
1277
|
finally {
|
|
@@ -1119,8 +1279,22 @@ async function linkScope(params) {
|
|
|
1119
1279
|
}
|
|
1120
1280
|
}
|
|
1121
1281
|
catch (err) {
|
|
1122
|
-
|
|
1123
|
-
|
|
1282
|
+
const repairTargets = repairRequiredPlatforms.filter((platform) => platforms.includes(platform) || platform === GENERAL_PLATFORM);
|
|
1283
|
+
const repairFailure = repairTargets.length > 0;
|
|
1284
|
+
if (repairTargets.length > 0) {
|
|
1285
|
+
pLog.error(`${dependency.libName} ${repairTargets.join(', ')} 本地缓存不完整,自动重新下载失败`);
|
|
1286
|
+
pLog.hint(`可运行 td reset ${dependency.libName} 后重试`);
|
|
1287
|
+
debug(`${dependency.libName} 下载失败: ${err.message}`);
|
|
1288
|
+
}
|
|
1289
|
+
else {
|
|
1290
|
+
pLog.error(`${dependency.libName} 下载失败: ${err.message}`);
|
|
1291
|
+
}
|
|
1292
|
+
return {
|
|
1293
|
+
success: false,
|
|
1294
|
+
name: dependency.libName,
|
|
1295
|
+
error: err.message,
|
|
1296
|
+
repairFailure,
|
|
1297
|
+
};
|
|
1124
1298
|
}
|
|
1125
1299
|
}));
|
|
1126
1300
|
const results = await Promise.all(downloadTasks);
|
|
@@ -1131,6 +1305,12 @@ async function linkScope(params) {
|
|
|
1131
1305
|
info(`下载完成: ${succeeded.length}/${toDownload.length} 个库`);
|
|
1132
1306
|
if (failed.length > 0)
|
|
1133
1307
|
warn(`${failed.length} 个库下载失败`);
|
|
1308
|
+
const failedRepairs = failed.filter((result) => 'repairFailure' in result && result.repairFailure);
|
|
1309
|
+
if (failedRepairs.length > 0) {
|
|
1310
|
+
const names = failedRepairs.map((result) => result.name).join(', ');
|
|
1311
|
+
debug(`[link] 缓存修复下载失败,终止链接: libraries=${names}`);
|
|
1312
|
+
throw new Error(`缓存修复下载失败: ${names}`);
|
|
1313
|
+
}
|
|
1134
1314
|
const allSkipped = [];
|
|
1135
1315
|
for (const r of results) {
|
|
1136
1316
|
if ('skippedPlatforms' in r && r.skippedPlatforms && r.skippedPlatforms.length > 0) {
|
|
@@ -1207,7 +1387,10 @@ async function linkScope(params) {
|
|
|
1207
1387
|
export async function linkProject(projectPath, options) {
|
|
1208
1388
|
const { absolutePath, normalizedPath, configPath: discoveredConfigPath, } = await resolveProjectRootPath(projectPath);
|
|
1209
1389
|
// === 阶段 1: 初始化 ===
|
|
1210
|
-
const cfg = await config.load();
|
|
1390
|
+
const cfg = await config.load({ migrate: !options.dryRun });
|
|
1391
|
+
if (options.dryRun && cfg && config.checkConfigVersion(cfg) === 'migrate') {
|
|
1392
|
+
throw new Error('配置需要迁移;dry-run 不会修改配置,请先运行一次普通命令完成迁移');
|
|
1393
|
+
}
|
|
1211
1394
|
if (cfg?.logLevel)
|
|
1212
1395
|
setLogLevel(cfg.logLevel);
|
|
1213
1396
|
if (cfg?.proxy)
|
|
@@ -1215,7 +1398,7 @@ export async function linkProject(projectPath, options) {
|
|
|
1215
1398
|
const concurrency = cfg?.concurrency ?? 5;
|
|
1216
1399
|
const gitLightweightDownload = cfg?.gitLightweightDownload ?? true;
|
|
1217
1400
|
const registry = getRegistry();
|
|
1218
|
-
await registry.load();
|
|
1401
|
+
await registry.load({ migrate: !options.dryRun });
|
|
1219
1402
|
const existingProject = registry.getProjectByPath(normalizedPath);
|
|
1220
1403
|
const rememberedPlatforms = existingProject?.platforms;
|
|
1221
1404
|
// 确定平台列表
|
|
@@ -1348,24 +1531,37 @@ export async function linkProject(projectPath, options) {
|
|
|
1348
1531
|
}
|
|
1349
1532
|
}
|
|
1350
1533
|
const finalPath = normalizedRoot;
|
|
1351
|
-
//
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1534
|
+
// 预演模式只读取现状,不恢复历史事务,也不创建新的持久事务日志。
|
|
1535
|
+
if (!options.dryRun) {
|
|
1536
|
+
const pendingTx = await Transaction.findPending();
|
|
1537
|
+
if (pendingTx) {
|
|
1538
|
+
warn(`发现未完成的事务 (${pendingTx.id.slice(0, 8)})`);
|
|
1539
|
+
info('正在尝试回滚...');
|
|
1540
|
+
try {
|
|
1541
|
+
await pendingTx.rollback();
|
|
1542
|
+
success('事务回滚完成');
|
|
1543
|
+
}
|
|
1544
|
+
catch (err) {
|
|
1545
|
+
error(`回滚失败: ${err.message}`);
|
|
1546
|
+
hint('请手动检查 Store 和项目目录状态');
|
|
1547
|
+
}
|
|
1363
1548
|
}
|
|
1364
1549
|
}
|
|
1550
|
+
else {
|
|
1551
|
+
const pendingTransactions = await Transaction.getPendingTransactions();
|
|
1552
|
+
debug(`[link] dry-run 跳过事务恢复与事务日志创建: pending=${pendingTransactions.length}`);
|
|
1553
|
+
}
|
|
1365
1554
|
const storePath = await store.getStorePath();
|
|
1555
|
+
if (!options.dryRun) {
|
|
1556
|
+
const recovered = await new SpaceService(registry, storePath)
|
|
1557
|
+
.createCleanupExecutor()
|
|
1558
|
+
.recoverPending();
|
|
1559
|
+
debug(`[link] 清理日志恢复完成: recovered=${recovered}, store=${storePath}`);
|
|
1560
|
+
}
|
|
1366
1561
|
const projectHash = registry.hashPath(finalPath);
|
|
1367
1562
|
const tx = new Transaction(`link:${finalPath}`);
|
|
1368
|
-
|
|
1563
|
+
if (!options.dryRun)
|
|
1564
|
+
await tx.begin();
|
|
1369
1565
|
// === 阶段 4: 依次执行 linkScope ===
|
|
1370
1566
|
try {
|
|
1371
1567
|
// 主项目
|
|
@@ -1514,52 +1710,44 @@ export async function linkProject(projectPath, options) {
|
|
|
1514
1710
|
if (allSavedBytes > 0) {
|
|
1515
1711
|
info(`本次节省: ${formatSize(allSavedBytes)}`);
|
|
1516
1712
|
}
|
|
1517
|
-
const
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1713
|
+
const spaceService = new SpaceService(registry, storePath);
|
|
1714
|
+
const spaceSnapshot = await spaceService.buildSnapshot('summary');
|
|
1715
|
+
info(`Store 总计: ${formatSize(spaceSnapshot.totals.physical.bytes)}`);
|
|
1716
|
+
const cleanupCandidates = spaceService.getCleanupCandidates(spaceSnapshot);
|
|
1717
|
+
const allCleanup = buildUnreferencedSelectionSummary(cleanupCandidates);
|
|
1718
|
+
if (allCleanup.platformCount > 0) {
|
|
1719
|
+
hint(`无引用: ${allCleanup.platformCount} 个 (约 ${formatSize(allCleanup.registeredBytes)}) - 可用 td clean 清理`);
|
|
1523
1720
|
// capacity 策略:检查是否超过阈值
|
|
1524
1721
|
const cleanStrategy = await config.get('cleanStrategy');
|
|
1525
1722
|
if (cleanStrategy === 'capacity') {
|
|
1526
1723
|
const threshold = (await config.get('unreferencedThreshold')) ?? 10 * 1024 * 1024 * 1024;
|
|
1527
|
-
if (
|
|
1724
|
+
if (allCleanup.registeredBytes > threshold) {
|
|
1528
1725
|
blank();
|
|
1529
|
-
warn(`无引用库容量 (${formatSize(
|
|
1726
|
+
warn(`无引用库容量 (约 ${formatSize(allCleanup.registeredBytes)}) 已超过阈值 (${formatSize(threshold)})`);
|
|
1530
1727
|
if (!options.yes && process.stdout.isTTY) {
|
|
1531
|
-
|
|
1532
|
-
const halfCleanStores = registry.getStoresForHalfClean();
|
|
1533
|
-
const halfCleanSize = halfCleanStores.reduce((sum, e) => sum + e.size, 0);
|
|
1728
|
+
const halfCleanup = buildHalfCleanupSelectionSummary(cleanupCandidates);
|
|
1534
1729
|
const action = await selectOption('选择清理方式:', [
|
|
1535
1730
|
{ name: '跳过本次', value: 'skip' },
|
|
1536
1731
|
{
|
|
1537
|
-
name: `清理最久未引用的 (释放约 ${formatSize(
|
|
1732
|
+
name: `清理最久未引用的 (释放约 ${formatSize(halfCleanup.registeredBytes)})`,
|
|
1538
1733
|
value: 'half',
|
|
1539
1734
|
},
|
|
1540
|
-
{ name: `全部清理 (
|
|
1735
|
+
{ name: `全部清理 (释放约 ${formatSize(allCleanup.registeredBytes)})`, value: 'all' },
|
|
1541
1736
|
]);
|
|
1542
1737
|
// ESC 取消视为跳过
|
|
1543
1738
|
if (action !== PROMPT_CANCELLED && action !== 'skip') {
|
|
1544
|
-
const
|
|
1739
|
+
const cleanup = action === 'half' ? halfCleanup : allCleanup;
|
|
1740
|
+
const plan = await spaceService.buildCleanupPlan({
|
|
1741
|
+
reason: 'capacity',
|
|
1742
|
+
selections: cleanup.selections,
|
|
1743
|
+
requireUnreferenced: true,
|
|
1744
|
+
});
|
|
1745
|
+
for (const planWarning of plan.warnings)
|
|
1746
|
+
warn(`[space] ${planWarning}`);
|
|
1545
1747
|
blank();
|
|
1546
|
-
info(
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
for (const entry of toClean) {
|
|
1550
|
-
try {
|
|
1551
|
-
await store.remove(entry.libName, entry.commit, entry.platform);
|
|
1552
|
-
const storeKey = registry.getStoreKey(entry.libName, entry.commit, entry.platform);
|
|
1553
|
-
registry.removeStore(storeKey);
|
|
1554
|
-
freedSize += entry.size;
|
|
1555
|
-
cleaned++;
|
|
1556
|
-
}
|
|
1557
|
-
catch (cleanErr) {
|
|
1558
|
-
warn(`清理 ${entry.libName}/${entry.platform} 失败: ${cleanErr.message}`);
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
await registry.save();
|
|
1562
|
-
success(`清理完成: 删除 ${cleaned} 个库,释放 ${formatSize(freedSize)}`);
|
|
1748
|
+
info(`正在执行清理计划 ${plan.id}...`);
|
|
1749
|
+
const cleanupResult = await spaceService.createCleanupExecutor().execute(plan);
|
|
1750
|
+
success(`清理完成: 删除 ${cleanupResult.succeeded.length} 项,释放 ${formatSize(cleanupResult.released.bytes)}`);
|
|
1563
1751
|
}
|
|
1564
1752
|
}
|
|
1565
1753
|
else {
|
|
@@ -1577,14 +1765,16 @@ export async function linkProject(projectPath, options) {
|
|
|
1577
1765
|
// 链接过程出错,回滚事务
|
|
1578
1766
|
error(`链接失败: ${err.message}`);
|
|
1579
1767
|
hintStoreRepairCommand(err);
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1768
|
+
if (!options.dryRun) {
|
|
1769
|
+
warn('正在回滚事务...');
|
|
1770
|
+
try {
|
|
1771
|
+
await tx.rollback();
|
|
1772
|
+
success('事务已回滚');
|
|
1773
|
+
}
|
|
1774
|
+
catch (rollbackErr) {
|
|
1775
|
+
error(`回滚失败: ${rollbackErr.message}`);
|
|
1776
|
+
hint('请手动检查 Store 和项目目录状态');
|
|
1777
|
+
}
|
|
1588
1778
|
}
|
|
1589
1779
|
process.exit(1);
|
|
1590
1780
|
}
|
|
@@ -1673,14 +1863,11 @@ async function ensureStoreInfo(libName, commit, platform, branch, url, projectHa
|
|
|
1673
1863
|
lastAccess: new Date().toISOString(),
|
|
1674
1864
|
});
|
|
1675
1865
|
}
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
registry.updateStore(storeKey, integrity);
|
|
1679
|
-
}
|
|
1680
|
-
if (existingEntry && ((!existingEntry.branch && branch) || (!existingEntry.url && url))) {
|
|
1866
|
+
const currentEntry = registry.getStore(storeKey);
|
|
1867
|
+
if (currentEntry && ((!currentEntry.branch && branch) || (!currentEntry.url && url))) {
|
|
1681
1868
|
registry.updateStore(storeKey, {
|
|
1682
|
-
branch:
|
|
1683
|
-
url:
|
|
1869
|
+
branch: currentEntry.branch || branch,
|
|
1870
|
+
url: currentEntry.url || url,
|
|
1684
1871
|
lastAccess: new Date().toISOString(),
|
|
1685
1872
|
});
|
|
1686
1873
|
}
|
|
@@ -1895,7 +2082,7 @@ export async function classifyLinkStatus(localPath, storeCommitPath, isGeneral,
|
|
|
1895
2082
|
* @param configPath 配置文件路径
|
|
1896
2083
|
* @param platforms 请求的平台列表
|
|
1897
2084
|
*/
|
|
1898
|
-
async function classifyDependencies(dependencies, projectPath, configPath, platforms, vars) {
|
|
2085
|
+
async function classifyDependencies(dependencies, projectPath, configPath, platforms, vars, repairStore = true) {
|
|
1899
2086
|
const result = [];
|
|
1900
2087
|
const thirdPartyDir = path.dirname(configPath);
|
|
1901
2088
|
const storePath = await store.getStorePath();
|
|
@@ -1909,24 +2096,33 @@ async function classifyDependencies(dependencies, projectPath, configPath, platf
|
|
|
1909
2096
|
const blockedPlatforms = getBlockedRequestedPlatforms(getRegistry(), dep, platforms, vars);
|
|
1910
2097
|
const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
|
|
1911
2098
|
// 检查 Store 中是否有任意请求的平台(支持 sparse 承载目录)
|
|
1912
|
-
const { actualExisting: rawExisting, satisfiedRequested, } = await resolveRequestedStoreState(dep, requestedPlatforms, vars);
|
|
2099
|
+
const { actualExisting: rawExisting, satisfiedRequested, } = await resolveRequestedStoreState(dep, requestedPlatforms, vars, repairStore);
|
|
2100
|
+
const registry = getRegistry();
|
|
2101
|
+
const libKey = registry.getLibraryKey(dep.libName, dep.commit);
|
|
2102
|
+
const library = registry.getLibrary(libKey);
|
|
1913
2103
|
// 完整性校验:验证 existing 平台的文件完整性
|
|
1914
|
-
const { valid: verifiedExisting, corrupted } = await verifyExistingPlatforms(dep.libName, dep.commit, rawExisting);
|
|
2104
|
+
const { valid: verifiedExisting, corrupted, repairRequired } = await verifyExistingPlatforms(dep.libName, dep.commit, rawExisting);
|
|
1915
2105
|
// 清除损坏的平台数据
|
|
1916
|
-
if (corrupted.length > 0) {
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
2106
|
+
if (repairStore && (corrupted.length > 0 || repairRequired.length > 0)) {
|
|
2107
|
+
await executeLinkStoreRepair(dep, [...new Set([...corrupted, ...repairRequired])], { category: 'classified-platform-corruption' });
|
|
2108
|
+
}
|
|
2109
|
+
let existing = verifiedExisting;
|
|
2110
|
+
const repairRequiredPlatforms = [...repairRequired];
|
|
2111
|
+
if (library?.isGeneral === true &&
|
|
2112
|
+
!(await hasRequestedPlatformContent(dep, requestedPlatforms, vars))) {
|
|
2113
|
+
if (repairStore)
|
|
2114
|
+
await prepareGeneralStoreRepair(dep, repairRequiredPlatforms);
|
|
2115
|
+
}
|
|
2116
|
+
if (repairStore && existing.length > 0) {
|
|
2117
|
+
const sharedRepairPlatforms = await prepareSharedStoreRepair(dep, existing, repairRequiredPlatforms);
|
|
2118
|
+
if (sharedRepairPlatforms.length > 0) {
|
|
2119
|
+
existing = existing.filter((platform) => !sharedRepairPlatforms.includes(platform));
|
|
1923
2120
|
}
|
|
1924
2121
|
}
|
|
1925
|
-
const existing = verifiedExisting;
|
|
1926
2122
|
// 也检查是否为 General 库(有 _shared 且有内容)
|
|
1927
|
-
const isGeneral = await resolveLibraryGeneralState(dep, requestedPlatforms, vars);
|
|
2123
|
+
const isGeneral = await resolveLibraryGeneralState(dep, requestedPlatforms, vars, repairStore);
|
|
1928
2124
|
const inStore = existing.length > 0 || isGeneral;
|
|
1929
|
-
if (satisfiedRequested.length > 0) {
|
|
2125
|
+
if (repairStore && satisfiedRequested.length > 0) {
|
|
1930
2126
|
clearSatisfiedAutoUnavailablePlatforms(getRegistry(), dep, satisfiedRequested);
|
|
1931
2127
|
}
|
|
1932
2128
|
// 用于非 inStore 情况的本地路径状态检查
|
|
@@ -2004,6 +2200,7 @@ async function classifyDependencies(dependencies, projectPath, configPath, platf
|
|
|
2004
2200
|
status,
|
|
2005
2201
|
localPath,
|
|
2006
2202
|
storePath: storeLibPath,
|
|
2203
|
+
repairRequiredPlatforms,
|
|
2007
2204
|
});
|
|
2008
2205
|
}
|
|
2009
2206
|
return result;
|
|
@@ -2012,6 +2209,7 @@ async function supplementMissingPlatforms(dependency, platforms, registry, tx, o
|
|
|
2012
2209
|
const result = { downloaded: [], unavailable: [], nestedLibraries: [] };
|
|
2013
2210
|
const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, options.vars);
|
|
2014
2211
|
const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
|
|
2212
|
+
const repairRequiredPlatforms = new Set(options.repairRequiredPlatforms ?? []);
|
|
2015
2213
|
// 1. 检查平台完整性
|
|
2016
2214
|
const { missingRequested, satisfiedRequested } = await resolveRequestedStoreState(dependency, requestedPlatforms, options.vars);
|
|
2017
2215
|
const missing = missingRequested;
|
|
@@ -2025,6 +2223,7 @@ async function supplementMissingPlatforms(dependency, platforms, registry, tx, o
|
|
|
2025
2223
|
const toDownload = options.forceDownload
|
|
2026
2224
|
? missing
|
|
2027
2225
|
: missing.filter((p) => !unavailablePlatforms.includes(p));
|
|
2226
|
+
const repairDownloadTargets = toDownload.filter((platform) => repairRequiredPlatforms.has(platform));
|
|
2028
2227
|
if (toDownload.length === 0) {
|
|
2029
2228
|
// 所有缺失平台都是已知不可用的
|
|
2030
2229
|
if (missing.length > 0) {
|
|
@@ -2034,7 +2233,12 @@ async function supplementMissingPlatforms(dependency, platforms, registry, tx, o
|
|
|
2034
2233
|
return result;
|
|
2035
2234
|
}
|
|
2036
2235
|
// 4. 尝试下载
|
|
2037
|
-
|
|
2236
|
+
if (repairDownloadTargets.length > 0) {
|
|
2237
|
+
debug(`${dependency.libName} ${repairDownloadTargets.join(', ')} 本地缓存不完整,开始重新下载`);
|
|
2238
|
+
}
|
|
2239
|
+
else {
|
|
2240
|
+
info(`${dependency.libName} 缺少平台 [${toDownload.join(', ')}],尝试下载...`);
|
|
2241
|
+
}
|
|
2038
2242
|
try {
|
|
2039
2243
|
const downloadResult = await codepac.downloadToTemp({
|
|
2040
2244
|
url: dependency.url,
|
|
@@ -2056,6 +2260,10 @@ async function supplementMissingPlatforms(dependency, platforms, registry, tx, o
|
|
|
2056
2260
|
const downloaded = assessment.downloadedRequested;
|
|
2057
2261
|
result.downloaded = downloaded;
|
|
2058
2262
|
clearSatisfiedAutoUnavailablePlatforms(registry, dependency, assessment.satisfiedRequested);
|
|
2263
|
+
const repaired = downloaded.filter((platform) => repairRequiredPlatforms.has(platform));
|
|
2264
|
+
if (repaired.length > 0) {
|
|
2265
|
+
warn(`${dependency.libName} ${repaired.join(', ')} 本地缓存不完整,已重新下载`);
|
|
2266
|
+
}
|
|
2059
2267
|
// 6. 记录新发现的不可用平台
|
|
2060
2268
|
const notFound = assessment.unavailableRequested;
|
|
2061
2269
|
if (notFound.length > 0) {
|
|
@@ -2095,7 +2303,9 @@ async function supplementMissingPlatforms(dependency, platforms, registry, tx, o
|
|
|
2095
2303
|
}
|
|
2096
2304
|
// 注册 _shared 目录(如果存在)
|
|
2097
2305
|
await registerSharedStore(dependency.libName, dependency.commit, dependency.branch, dependency.url);
|
|
2098
|
-
|
|
2306
|
+
if (repaired.length === 0) {
|
|
2307
|
+
success(`${dependency.libName} 已补充平台 [${downloaded.join(', ')}]`);
|
|
2308
|
+
}
|
|
2099
2309
|
}
|
|
2100
2310
|
}
|
|
2101
2311
|
finally {
|
|
@@ -2104,7 +2314,16 @@ async function supplementMissingPlatforms(dependency, platforms, registry, tx, o
|
|
|
2104
2314
|
}
|
|
2105
2315
|
}
|
|
2106
2316
|
catch (err) {
|
|
2107
|
-
|
|
2317
|
+
const repairTargets = toDownload.filter((platform) => repairRequiredPlatforms.has(platform));
|
|
2318
|
+
if (repairTargets.length > 0) {
|
|
2319
|
+
error(`${dependency.libName} ${repairTargets.join(', ')} 本地缓存不完整,自动重新下载失败`);
|
|
2320
|
+
hint(`可运行 td reset ${dependency.libName} 后重试`);
|
|
2321
|
+
debug(`${dependency.libName} 下载失败: ${err.message}`);
|
|
2322
|
+
throw err;
|
|
2323
|
+
}
|
|
2324
|
+
else {
|
|
2325
|
+
warn(`${dependency.libName} 下载失败: ${err.message}`);
|
|
2326
|
+
}
|
|
2108
2327
|
}
|
|
2109
2328
|
return result;
|
|
2110
2329
|
}
|
|
@@ -2358,6 +2577,7 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2358
2577
|
const knownUnavailable = platforms.filter(p => unavailablePlatforms.includes(p));
|
|
2359
2578
|
let storeHas = false;
|
|
2360
2579
|
let existingPlatforms = [];
|
|
2580
|
+
let repairRequiredPlatforms = [];
|
|
2361
2581
|
const initialStoreState = await resolveRequestedStoreState(dep, availablePlatforms, vars);
|
|
2362
2582
|
existingPlatforms.push(...initialStoreState.actualExisting);
|
|
2363
2583
|
if (initialStoreState.satisfiedRequested.length > 0) {
|
|
@@ -2366,15 +2586,27 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2366
2586
|
storeHas = existingPlatforms.length > 0;
|
|
2367
2587
|
// 完整性校验:验证 existing 平台的文件完整性(与 classifyDependencies 同逻辑)
|
|
2368
2588
|
if (existingPlatforms.length > 0) {
|
|
2369
|
-
const { valid: verifiedPlatforms, corrupted } = await verifyExistingPlatforms(dep.libName, dep.commit, existingPlatforms);
|
|
2589
|
+
const { valid: verifiedPlatforms, corrupted, repairRequired } = await verifyExistingPlatforms(dep.libName, dep.commit, existingPlatforms);
|
|
2370
2590
|
existingPlatforms = verifiedPlatforms;
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2591
|
+
repairRequiredPlatforms = repairRequired;
|
|
2592
|
+
if (corrupted.length > 0 || repairRequired.length > 0) {
|
|
2593
|
+
await executeLinkStoreRepair(dep, [...new Set([...corrupted, ...repairRequired])], { category: 'nested-platform-corruption' });
|
|
2594
|
+
}
|
|
2595
|
+
storeHas = existingPlatforms.length > 0;
|
|
2596
|
+
}
|
|
2597
|
+
const nestedLibKey = registry.getLibraryKey(dep.libName, dep.commit);
|
|
2598
|
+
const nestedLibrary = registry.getLibrary(nestedLibKey);
|
|
2599
|
+
if (nestedLibrary?.isGeneral === true &&
|
|
2600
|
+
!(await hasRequestedPlatformContent(dep, availablePlatforms, vars))) {
|
|
2601
|
+
const generalNeedsRepair = await prepareGeneralStoreRepair(dep, repairRequiredPlatforms);
|
|
2602
|
+
if (generalNeedsRepair) {
|
|
2603
|
+
storeHas = existingPlatforms.length > 0;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
if (existingPlatforms.length > 0) {
|
|
2607
|
+
const sharedRepairPlatforms = await prepareSharedStoreRepair(dep, existingPlatforms, repairRequiredPlatforms);
|
|
2608
|
+
if (sharedRepairPlatforms.length > 0) {
|
|
2609
|
+
existingPlatforms = existingPlatforms.filter((platform) => !sharedRepairPlatforms.includes(platform));
|
|
2378
2610
|
}
|
|
2379
2611
|
storeHas = existingPlatforms.length > 0;
|
|
2380
2612
|
}
|
|
@@ -2468,7 +2700,7 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2468
2700
|
let linkedPlatforms = isGeneral ? [GENERAL_PLATFORM] : [...existingPlatforms];
|
|
2469
2701
|
// 已链接,但需要检查是否需要补充缺失平台(与顶层依赖逻辑一致)
|
|
2470
2702
|
if (!isGeneral) {
|
|
2471
|
-
const supplementResult = await supplementMissingPlatforms(dep, platforms, registry, tx, { vars, useGitLightweightDownload: options.gitLightweightDownload });
|
|
2703
|
+
const supplementResult = await supplementMissingPlatforms(dep, platforms, registry, tx, { vars, useGitLightweightDownload: options.gitLightweightDownload, repairRequiredPlatforms });
|
|
2472
2704
|
// 注册嵌套依赖
|
|
2473
2705
|
if (supplementResult.nestedLibraries.length > 0) {
|
|
2474
2706
|
await registerNestedLibraries(supplementResult.nestedLibraries, projectHash);
|
|
@@ -2539,7 +2771,7 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2539
2771
|
}
|
|
2540
2772
|
else {
|
|
2541
2773
|
// 平台库:先补充缺失平台(与顶层依赖逻辑一致)
|
|
2542
|
-
const supplementResult = await supplementMissingPlatforms(dep, platforms, registry, tx, { vars, useGitLightweightDownload: options.gitLightweightDownload });
|
|
2774
|
+
const supplementResult = await supplementMissingPlatforms(dep, platforms, registry, tx, { vars, useGitLightweightDownload: options.gitLightweightDownload, repairRequiredPlatforms });
|
|
2543
2775
|
// 注册嵌套依赖
|
|
2544
2776
|
if (supplementResult.nestedLibraries.length > 0) {
|
|
2545
2777
|
await registerNestedLibraries(supplementResult.nestedLibraries, projectHash);
|
|
@@ -2565,7 +2797,13 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2565
2797
|
}
|
|
2566
2798
|
else if (download) {
|
|
2567
2799
|
// Store 没有,需要下载
|
|
2568
|
-
|
|
2800
|
+
const repairTargets = repairRequiredPlatforms.filter((platform) => availablePlatforms.includes(platform) || platform === GENERAL_PLATFORM);
|
|
2801
|
+
if (repairTargets.length > 0) {
|
|
2802
|
+
debug(`${indent} ${dep.libName} ${repairTargets.join(', ')} 本地缓存不完整,开始重新下载`);
|
|
2803
|
+
}
|
|
2804
|
+
else {
|
|
2805
|
+
info(`${indent} ${dep.libName} - 下载中...`);
|
|
2806
|
+
}
|
|
2569
2807
|
if (dryRun) {
|
|
2570
2808
|
info(`${indent} ${dep.libName} - 将下载`);
|
|
2571
2809
|
continue;
|
|
@@ -2623,7 +2861,12 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2623
2861
|
platform: GENERAL_PLATFORM,
|
|
2624
2862
|
linkedPath: path.relative(projectRoot, localPath),
|
|
2625
2863
|
});
|
|
2626
|
-
|
|
2864
|
+
if (repairTargets.includes(GENERAL_PLATFORM)) {
|
|
2865
|
+
warn(`${indent} ${dep.libName} ${GENERAL_PLATFORM} 本地缓存不完整,已重新下载`);
|
|
2866
|
+
}
|
|
2867
|
+
else {
|
|
2868
|
+
success(`${indent} ${dep.libName} - 下载完成 (General)`);
|
|
2869
|
+
}
|
|
2627
2870
|
}
|
|
2628
2871
|
else if (assessment.downloadedRequested.length > 0) {
|
|
2629
2872
|
// 平台库处理
|
|
@@ -2652,7 +2895,13 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2652
2895
|
});
|
|
2653
2896
|
warn(`${indent} ${dep.libName} 平台 [${newUnavailable.join(', ')}] 远程不存在,已记录`);
|
|
2654
2897
|
}
|
|
2655
|
-
|
|
2898
|
+
const repaired = assessment.downloadedRequested.filter((platform) => repairRequiredPlatforms.includes(platform));
|
|
2899
|
+
if (repaired.length > 0) {
|
|
2900
|
+
warn(`${indent} ${dep.libName} ${repaired.join(', ')} 本地缓存不完整,已重新下载`);
|
|
2901
|
+
}
|
|
2902
|
+
else {
|
|
2903
|
+
success(`${indent} ${dep.libName} - 下载完成 [${assessment.downloadedRequested.join(', ')}]`);
|
|
2904
|
+
}
|
|
2656
2905
|
}
|
|
2657
2906
|
else {
|
|
2658
2907
|
// 所有请求的平台都不可用,记录到 registry
|
|
@@ -2672,7 +2921,15 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2672
2921
|
}
|
|
2673
2922
|
}
|
|
2674
2923
|
catch (err) {
|
|
2675
|
-
|
|
2924
|
+
if (repairTargets.length > 0) {
|
|
2925
|
+
error(`${indent} ${dep.libName} ${repairTargets.join(', ')} 本地缓存不完整,自动重新下载失败`);
|
|
2926
|
+
hint(`${indent} 可运行 td reset ${dep.libName} 后重试`);
|
|
2927
|
+
debug(`${indent} ${dep.libName} 下载失败: ${err.message}`);
|
|
2928
|
+
throw err;
|
|
2929
|
+
}
|
|
2930
|
+
else {
|
|
2931
|
+
warn(`${indent} ${dep.libName} - 下载失败: ${err.message}`);
|
|
2932
|
+
}
|
|
2676
2933
|
}
|
|
2677
2934
|
}
|
|
2678
2935
|
else {
|
|
@@ -2703,73 +2960,37 @@ function mergeDepLists(main, optional) {
|
|
|
2703
2960
|
return Array.from(depMap.values());
|
|
2704
2961
|
}
|
|
2705
2962
|
/**
|
|
2706
|
-
*
|
|
2707
|
-
*
|
|
2708
|
-
*
|
|
2709
|
-
* 没有 fileCount 的条目跳过校验(视为合法)。
|
|
2963
|
+
* 校验已有平台的完整性。
|
|
2964
|
+
* 只有带 fileCount 和 contentHash 的 StoreEntry 才被视为可验证缓存。
|
|
2965
|
+
* 缺失完整性记录的旧缓存不能证明自身完整,会被标记为需要重新下载。
|
|
2710
2966
|
*
|
|
2711
2967
|
* @param libName 库名
|
|
2712
2968
|
* @param commit commit hash
|
|
2713
2969
|
* @param existingPlatforms Store 中已有的平台列表
|
|
2714
|
-
* @returns { valid: string[]; corrupted: string[] }
|
|
2970
|
+
* @returns { valid: string[]; corrupted: string[]; repairRequired: string[] } 合法、损坏和需要重下的平台列表
|
|
2715
2971
|
*/
|
|
2716
2972
|
export async function verifyExistingPlatforms(libName, commit, existingPlatforms) {
|
|
2717
2973
|
const valid = [];
|
|
2718
2974
|
const corrupted = [];
|
|
2975
|
+
const repairRequired = [];
|
|
2719
2976
|
const registry = getRegistry();
|
|
2720
2977
|
for (const platform of existingPlatforms) {
|
|
2721
2978
|
const storeKey = registry.getStoreKey(libName, commit, platform);
|
|
2722
2979
|
const entry = registry.getStore(storeKey);
|
|
2980
|
+
const visiblePlatform = platform === GENERAL_PLATFORM ? '_shared' : platform;
|
|
2723
2981
|
if (!entry) {
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
if (integrity.fileCount === 0) {
|
|
2727
|
-
corrupted.push(platform);
|
|
2728
|
-
warn(`${libName} (${commit.slice(0, 7)}) ${platform}: Store 目录为空`);
|
|
2729
|
-
continue;
|
|
2730
|
-
}
|
|
2731
|
-
registry.addStore({
|
|
2732
|
-
libName,
|
|
2733
|
-
commit,
|
|
2734
|
-
platform,
|
|
2735
|
-
branch: '',
|
|
2736
|
-
url: '',
|
|
2737
|
-
...integrity,
|
|
2738
|
-
usedBy: [],
|
|
2739
|
-
createdAt: new Date().toISOString(),
|
|
2740
|
-
lastAccess: new Date().toISOString(),
|
|
2741
|
-
});
|
|
2742
|
-
}
|
|
2743
|
-
catch {
|
|
2744
|
-
corrupted.push(platform);
|
|
2745
|
-
warn(`${libName} (${commit.slice(0, 7)}) ${platform}: Store 完整性回填失败`);
|
|
2746
|
-
continue;
|
|
2747
|
-
}
|
|
2748
|
-
valid.push(platform);
|
|
2982
|
+
repairRequired.push(platform);
|
|
2983
|
+
debug(`${libName} (${commit.slice(0, 7)}) ${visiblePlatform}: Store 缺少 Registry 记录,准备重新下载`);
|
|
2749
2984
|
continue;
|
|
2750
2985
|
}
|
|
2751
2986
|
if (entry.fileCount === 0) {
|
|
2752
2987
|
corrupted.push(platform);
|
|
2753
|
-
warn(`${libName} (${commit.slice(0, 7)}) ${
|
|
2988
|
+
warn(`${libName} (${commit.slice(0, 7)}) ${visiblePlatform}: Store 记录为空`);
|
|
2754
2989
|
continue;
|
|
2755
2990
|
}
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
const integrity = await store.captureIntegrity(libName, commit, platform);
|
|
2760
|
-
registry.updateStore(storeKey, integrity);
|
|
2761
|
-
if (integrity.fileCount === 0) {
|
|
2762
|
-
corrupted.push(platform);
|
|
2763
|
-
warn(`${libName} (${commit.slice(0, 7)}) ${platform}: Store 目录为空`);
|
|
2764
|
-
continue;
|
|
2765
|
-
}
|
|
2766
|
-
}
|
|
2767
|
-
catch {
|
|
2768
|
-
corrupted.push(platform);
|
|
2769
|
-
warn(`${libName} (${commit.slice(0, 7)}) ${platform}: Store 完整性回填失败`);
|
|
2770
|
-
continue;
|
|
2771
|
-
}
|
|
2772
|
-
valid.push(platform);
|
|
2991
|
+
if (entry.fileCount == null || !entry.contentHash) {
|
|
2992
|
+
repairRequired.push(platform);
|
|
2993
|
+
debug(`${libName} (${commit.slice(0, 7)}) ${visiblePlatform}: Store 缺少完整性记录,准备重新下载`);
|
|
2773
2994
|
continue;
|
|
2774
2995
|
}
|
|
2775
2996
|
const result = await store.fullVerify(libName, commit, platform, {
|
|
@@ -2782,10 +3003,10 @@ export async function verifyExistingPlatforms(libName, commit, existingPlatforms
|
|
|
2782
3003
|
}
|
|
2783
3004
|
else {
|
|
2784
3005
|
corrupted.push(platform);
|
|
2785
|
-
warn(`${libName} (${commit.slice(0, 7)}) ${
|
|
3006
|
+
warn(`${libName} (${commit.slice(0, 7)}) ${visiblePlatform}: 完整性校验失败 - ${result.reason}`);
|
|
2786
3007
|
}
|
|
2787
3008
|
}
|
|
2788
|
-
return { valid, corrupted };
|
|
3009
|
+
return { valid, corrupted, repairRequired };
|
|
2789
3010
|
}
|
|
2790
3011
|
/**
|
|
2791
3012
|
* 创建带完整性信息的 StoreEntry
|