tanmi-dock 0.9.3 → 1.0.3

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.
Files changed (51) hide show
  1. package/README.md +10 -4
  2. package/dist/commands/check.d.ts +36 -0
  3. package/dist/commands/check.d.ts.map +1 -1
  4. package/dist/commands/check.js +61 -7
  5. package/dist/commands/check.js.map +1 -1
  6. package/dist/commands/config.d.ts.map +1 -1
  7. package/dist/commands/config.js +10 -0
  8. package/dist/commands/config.js.map +1 -1
  9. package/dist/commands/link.d.ts +3 -2
  10. package/dist/commands/link.d.ts.map +1 -1
  11. package/dist/commands/link.js +877 -598
  12. package/dist/commands/link.js.map +1 -1
  13. package/dist/commands/reset.d.ts.map +1 -1
  14. package/dist/commands/reset.js +46 -17
  15. package/dist/commands/reset.js.map +1 -1
  16. package/dist/commands/status.d.ts.map +1 -1
  17. package/dist/commands/status.js +145 -20
  18. package/dist/commands/status.js.map +1 -1
  19. package/dist/commands/unavailable.d.ts.map +1 -1
  20. package/dist/commands/unavailable.js +7 -2
  21. package/dist/commands/unavailable.js.map +1 -1
  22. package/dist/core/codepac.d.ts +46 -0
  23. package/dist/core/codepac.d.ts.map +1 -1
  24. package/dist/core/codepac.js +599 -63
  25. package/dist/core/codepac.js.map +1 -1
  26. package/dist/core/config.d.ts.map +1 -1
  27. package/dist/core/config.js +14 -0
  28. package/dist/core/config.js.map +1 -1
  29. package/dist/core/linker.d.ts +7 -0
  30. package/dist/core/linker.d.ts.map +1 -1
  31. package/dist/core/linker.js +47 -0
  32. package/dist/core/linker.js.map +1 -1
  33. package/dist/core/parser.d.ts +30 -5
  34. package/dist/core/parser.d.ts.map +1 -1
  35. package/dist/core/parser.js +375 -56
  36. package/dist/core/parser.js.map +1 -1
  37. package/dist/core/platform.js +2 -2
  38. package/dist/core/platform.js.map +1 -1
  39. package/dist/core/store.d.ts +2 -0
  40. package/dist/core/store.d.ts.map +1 -1
  41. package/dist/core/store.js +32 -6
  42. package/dist/core/store.js.map +1 -1
  43. package/dist/types/index.d.ts +63 -11
  44. package/dist/types/index.d.ts.map +1 -1
  45. package/dist/types/index.js +3 -2
  46. package/dist/types/index.js.map +1 -1
  47. package/dist/utils/git.d.ts +20 -1
  48. package/dist/utils/git.d.ts.map +1 -1
  49. package/dist/utils/git.js +181 -4
  50. package/dist/utils/git.js.map +1 -1
  51. package/package.json +1 -1
@@ -7,13 +7,13 @@ import { Command } from 'commander';
7
7
  import { ensureInitialized } from '../core/guard.js';
8
8
  import * as config from '../core/config.js';
9
9
  import { setLogLevel } from '../utils/logger.js';
10
- import { parseProjectDependencies, getRelativeConfigPath, parseCodepacDep, extractActions, parseActionCommand, extractNestedDependencies, findAllCodepacConfigs, extractDependencies, resolveProjectRootPath, } from '../core/parser.js';
10
+ import { parseProjectDependencies, getRelativeConfigPath, parseCodepacDep, extractActions, buildActionExecutionPlan, extractNestedDependencies, findAllCodepacConfigs, extractDependencies, resolveProjectRootPath, } from '../core/parser.js';
11
11
  import { getRegistry } from '../core/registry.js';
12
12
  import * as store from '../core/store.js';
13
13
  import * as linker from '../core/linker.js';
14
14
  import * as codepac from '../core/codepac.js';
15
15
  import { setProxyConfig } from '../core/codepac.js';
16
- import { getPlatformHelpText, GENERAL_PLATFORM, SHARED_PLATFORM, pathsEqual, isSparseOnlyCommon, KNOWN_PLATFORM_VALUES, getRequestedPlatformTargets, } from '../core/platform.js';
16
+ import { getPlatformHelpText, GENERAL_PLATFORM, SHARED_PLATFORM, pathsEqual, isSparseOnlyCommon, KNOWN_PLATFORM_VALUES, getRequestedPlatformTargets, resolveSparseConfig, getBaseKeyForCodepac, } from '../core/platform.js';
17
17
  import { Transaction } from '../core/transaction.js';
18
18
  import { formatSize, checkDiskSpace } from '../utils/disk.js';
19
19
  import { getDirSize } from '../utils/fs-utils.js';
@@ -37,6 +37,7 @@ export function createLinkCommand() {
37
37
  .option('--no-download', '不自动下载缺失库')
38
38
  .option('--dry-run', '只显示将要执行的操作')
39
39
  .option('--config <configs...>', '指定可选配置文件 (如 codepac-dep-inner.json)')
40
+ .option('--skip-action, --skip_action, -sa <names...>', '按 action.name 跳过指定嵌套 action')
40
41
  .option('--no-submodules', '不检测 git submodule 依赖')
41
42
  .addHelpText('after', `${getPlatformHelpText()}
42
43
 
@@ -48,6 +49,7 @@ export function createLinkCommand() {
48
49
  td link --dry-run 预览操作,不实际执行
49
50
  td link -y 跳过确认,自动执行
50
51
  td link --config codepac-dep-inner.json 使用指定的可选配置
52
+ td link --skip-action prepareNested 跳过指定 action.name
51
53
  td link --no-submodules 跳过 git submodule 检测`)
52
54
  .action(async (projectPath, options) => {
53
55
  await ensureInitialized();
@@ -97,8 +99,20 @@ async function resolveRequestedStoreState(dependency, requestedPlatforms, vars)
97
99
  if (await store.exists(dependency.libName, dependency.commit, platform)) {
98
100
  resolvedExisting.push(platform);
99
101
  }
102
+ else {
103
+ const registry = getRegistry();
104
+ const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
105
+ if (registry.getStore(storeKey)) {
106
+ registry.removeStore(storeKey);
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(() => { });
110
+ }
111
+ }
100
112
  }
101
- return resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, resolvedExisting, vars);
113
+ const result = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, resolvedExisting, vars);
114
+ 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
+ return result;
102
116
  }
103
117
  function getBlockedRequestedPlatforms(registry, dependency, requestedPlatforms, vars) {
104
118
  const { manualPlatforms, autoPlatforms } = resolveUnavailablePlatforms(registry, dependency);
@@ -109,7 +123,9 @@ function getBlockedRequestedPlatforms(registry, dependency, requestedPlatforms,
109
123
  const targets = getRequestedPlatformTargets(platform, dependency.sparse, vars);
110
124
  return targets.length === 1 && targets[0] === platform;
111
125
  });
112
- return [...new Set([...manualBlocked, ...autoBlocked])];
126
+ const blocked = [...new Set([...manualBlocked, ...autoBlocked])];
127
+ debug(`[link-platforms] ${dependency.libName}@${dependency.commit.slice(0, 7)} 请求平台=${requestedPlatforms.join(', ') || '空'}, 手动跳过=${manualBlocked.join(', ') || '无'}, 自动跳过=${autoBlocked.join(', ') || '无'}, 最终跳过=${blocked.join(', ') || '无'}`);
128
+ return blocked;
113
129
  }
114
130
  function clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested) {
115
131
  if (satisfiedRequested.length === 0)
@@ -154,12 +170,93 @@ function upsertLibraryAvailability(dependency, updates) {
154
170
  lastAccess: new Date().toISOString(),
155
171
  });
156
172
  }
157
- export async function resolveLibraryGeneralState(dependency) {
173
+ export async function resolveLibraryGeneralState(dependency, requestedPlatforms = [], vars) {
158
174
  const registry = getRegistry();
159
175
  const libKey = registry.getLibraryKey(dependency.libName, dependency.commit);
160
176
  const library = registry.getLibrary(libKey);
177
+ const sparseConfig = resolveSparseConfig(dependency.sparse, vars);
178
+ const hasPlatformSparse = sparseConfig
179
+ ? Object.keys(sparseConfig).some((key) => key !== 'common')
180
+ : Boolean(dependency.sparse && !isSparseOnlyCommon(dependency.sparse));
181
+ const hasPlatformLibraryHistory = requestedPlatforms.length > 0
182
+ && library?.isGeneral === false
183
+ && library.platforms.some((platform) => platform !== GENERAL_PLATFORM);
184
+ if (requestedPlatforms.length > 0) {
185
+ const requestedState = await resolveRequestedStoreState(dependency, requestedPlatforms, vars);
186
+ if (requestedState.actualExisting.length > 0) {
187
+ if (library?.isGeneral === true) {
188
+ registry.updateLibrary(libKey, {
189
+ platforms: requestedState.actualExisting,
190
+ isGeneral: false,
191
+ lastAccess: new Date().toISOString(),
192
+ });
193
+ }
194
+ return false;
195
+ }
196
+ }
197
+ if (requestedPlatforms.length > 0 && library?.isGeneral === true) {
198
+ const requestedTargets = [...new Set(requestedPlatforms.flatMap((platform) => getRequestedPlatformTargets(platform, dependency.sparse, vars)))];
199
+ const storePath = await store.getStorePath();
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 检测
218
+ }
219
+ }
220
+ if (requestedPlatforms.length > 0 && hasPlatformSparse) {
221
+ if (library?.isGeneral === true) {
222
+ registry.updateLibrary(libKey, {
223
+ isGeneral: false,
224
+ lastAccess: new Date().toISOString(),
225
+ });
226
+ }
227
+ return false;
228
+ }
229
+ if (hasPlatformLibraryHistory) {
230
+ return false;
231
+ }
161
232
  if (typeof library?.isGeneral === 'boolean') {
162
- return library.isGeneral;
233
+ if (library.isGeneral) {
234
+ const hasGeneralContent = await store.exists(dependency.libName, dependency.commit, GENERAL_PLATFORM);
235
+ if (!hasGeneralContent) {
236
+ registry.updateLibrary(libKey, {
237
+ isGeneral: false,
238
+ lastAccess: new Date().toISOString(),
239
+ });
240
+ return false;
241
+ }
242
+ const actualGeneral = await store.isGeneralLib(dependency.libName, dependency.commit);
243
+ if (!actualGeneral) {
244
+ registry.updateLibrary(libKey, {
245
+ isGeneral: false,
246
+ lastAccess: new Date().toISOString(),
247
+ });
248
+ }
249
+ return actualGeneral;
250
+ }
251
+ const actualGeneral = await store.isGeneralLib(dependency.libName, dependency.commit);
252
+ if (actualGeneral) {
253
+ registry.updateLibrary(libKey, {
254
+ platforms: [GENERAL_PLATFORM],
255
+ isGeneral: true,
256
+ lastAccess: new Date().toISOString(),
257
+ });
258
+ }
259
+ return actualGeneral;
163
260
  }
164
261
  return store.isGeneralLib(dependency.libName, dependency.commit);
165
262
  }
@@ -173,7 +270,7 @@ export function resolveUnavailablePlatforms(registry, dependency) {
173
270
  autoPlatforms,
174
271
  };
175
272
  }
176
- export async function collectProjectDependencyGraph(projectRoot) {
273
+ export async function collectProjectDependencyGraph(projectRoot, codepacPlatforms = []) {
177
274
  const { normalizedPath, configPath } = await resolveProjectRootPath(projectRoot);
178
275
  if (!configPath) {
179
276
  throw new Error('只能在项目目录下使用');
@@ -182,11 +279,17 @@ export async function collectProjectDependencyGraph(projectRoot) {
182
279
  const seenDeps = new Set();
183
280
  const visitedConfigs = new Set();
184
281
  const queue = [
185
- { configPath, source: 'direct' },
282
+ { configPath, targetDir: path.dirname(configPath), codepacPlatforms, source: 'direct' },
186
283
  ];
187
- const submodules = await findSubmoduleConfigs(normalizedPath);
284
+ const submodules = await findSubmoduleConfigs(normalizedPath, '', 0, codepacPlatforms);
188
285
  for (const submodule of submodules) {
189
- queue.push({ configPath: submodule.configPath, scope: submodule.relativePath, source: 'submodule' });
286
+ queue.push({
287
+ configPath: submodule.configPath,
288
+ targetDir: path.dirname(submodule.configPath),
289
+ codepacPlatforms,
290
+ scope: submodule.relativePath,
291
+ source: 'submodule',
292
+ });
190
293
  }
191
294
  while (queue.length > 0) {
192
295
  const current = queue.shift();
@@ -200,25 +303,32 @@ export async function collectProjectDependencyGraph(projectRoot) {
200
303
  catch {
201
304
  continue;
202
305
  }
203
- for (const dependency of extractDependencies(parsedConfig)) {
306
+ const extractOptions = { platforms: current.codepacPlatforms, configPath: current.configPath };
307
+ for (const dependency of extractDependencies(parsedConfig, extractOptions)) {
204
308
  const key = `${dependency.libName}:${dependency.commit}:${current.scope ?? ''}`;
205
309
  if (!seenDeps.has(key)) {
206
310
  seenDeps.add(key);
207
311
  results.push({ ...dependency, scope: current.scope, source: current.source });
208
312
  }
209
313
  }
210
- for (const action of extractActions(parsedConfig)) {
211
- let parsedAction;
314
+ for (const action of extractActions(parsedConfig, extractOptions)) {
315
+ let plan;
212
316
  try {
213
- parsedAction = parseActionCommand(action.command);
317
+ plan = buildActionExecutionPlan(action, {
318
+ parentConfigPath: current.configPath,
319
+ parentTargetDir: current.targetDir,
320
+ inheritedCodepacPlatforms: current.codepacPlatforms,
321
+ });
214
322
  }
215
323
  catch {
216
324
  continue;
217
325
  }
218
- const nestedConfigPath = path.resolve(path.dirname(current.configPath), parsedAction.configDir, 'codepac-dep.json');
219
326
  let nested;
220
327
  try {
221
- nested = await extractNestedDependencies(nestedConfigPath, parsedAction.libraries);
328
+ nested = await extractNestedDependencies(plan.nestedConfigPath, plan.libraries, {
329
+ platforms: plan.effectiveCodepacPlatforms,
330
+ configPath: plan.nestedConfigPath,
331
+ });
222
332
  }
223
333
  catch {
224
334
  continue;
@@ -230,7 +340,15 @@ export async function collectProjectDependencyGraph(projectRoot) {
230
340
  results.push({ ...dependency, scope: current.scope, source: 'nested' });
231
341
  }
232
342
  }
233
- queue.push({ configPath: nestedConfigPath, scope: current.scope, source: 'nested' });
343
+ if (!plan.parsed.disableAction) {
344
+ queue.push({
345
+ configPath: plan.nestedConfigPath,
346
+ targetDir: plan.nestedTargetDir,
347
+ codepacPlatforms: plan.effectiveCodepacPlatforms,
348
+ scope: current.scope,
349
+ source: 'nested',
350
+ });
351
+ }
234
352
  }
235
353
  }
236
354
  return results;
@@ -276,14 +394,25 @@ async function selectSubmodules(configs, options, remembered) {
276
394
  }
277
395
  return selected.map(s => ({ ...s }));
278
396
  }
397
+ function buildSubmoduleOptionalConfigState(submodules) {
398
+ const state = {};
399
+ for (const submodule of submodules) {
400
+ const names = submodule.selectedOptionalConfigs?.map((config) => config.name) ?? [];
401
+ if (names.length > 0) {
402
+ state[submodule.relativePath] = names;
403
+ }
404
+ }
405
+ return Object.keys(state).length > 0 ? state : undefined;
406
+ }
279
407
  /**
280
408
  * 单个 scope(主项目或 submodule)的链接处理
281
409
  *
282
410
  * 包含:解析依赖、分类、预扫描平台、下载确认、逐个处理、并行下载、嵌套 actions
283
411
  */
284
412
  async function linkScope(params) {
285
- const { scopeName, configPath, platforms, scanExtraPlatforms, registry, tx, projectHash, projectRoot, storePath, download, dryRun, yes, concurrency, optionalConfigs, scope, } = params;
413
+ const { scopeName, configPath, platforms, scanExtraPlatforms, registry, tx, projectHash, projectRoot, storePath, download, dryRun, yes, concurrency, gitLightweightDownload, optionalConfigs, scope, } = params;
286
414
  let finalLinkPlatforms = [...params.finalLinkPlatforms];
415
+ const codepacPlatforms = [...new Set(platforms.map((platform) => getBaseKeyForCodepac(platform)))];
287
416
  // 记录 General 类型库
288
417
  const generalLibs = new Set();
289
418
  const downloadedLibs = [];
@@ -292,17 +421,32 @@ async function linkScope(params) {
292
421
  info(`分析 ${scopeName}: ${configPath}`);
293
422
  let dependencies;
294
423
  let configVars;
424
+ const selectedActions = [];
425
+ const cacheConfigPaths = [configPath];
295
426
  try {
296
- const result = await parseProjectDependencies(path.dirname(path.dirname(configPath)));
427
+ const result = await parseProjectDependencies(path.dirname(path.dirname(configPath)), {
428
+ platforms: codepacPlatforms,
429
+ });
297
430
  dependencies = result.dependencies;
298
431
  configVars = result.vars;
432
+ const topLevelConfig = await parseCodepacDep(configPath);
433
+ selectedActions.push(...extractActions(topLevelConfig, { platforms: codepacPlatforms, configPath }));
299
434
  // 合并可选配置依赖
300
435
  if (optionalConfigs && optionalConfigs.length > 0) {
301
436
  for (const optionalConfig of optionalConfigs) {
302
437
  try {
303
438
  const optionalResult = await parseCodepacDep(optionalConfig.path);
304
- const optionalDeps = extractDependencies(optionalResult);
439
+ const optionalDeps = extractDependencies(optionalResult, {
440
+ platforms: codepacPlatforms,
441
+ configPath: optionalConfig.path,
442
+ });
305
443
  dependencies = mergeDepLists(dependencies, optionalDeps);
444
+ configVars = { ...configVars, ...optionalResult.vars };
445
+ selectedActions.push(...extractActions(optionalResult, {
446
+ platforms: codepacPlatforms,
447
+ configPath: optionalConfig.path,
448
+ }));
449
+ cacheConfigPaths.push(optionalConfig.path);
306
450
  info(` + ${optionalConfig.name}: ${optionalDeps.length} 个依赖`);
307
451
  }
308
452
  catch (optErr) {
@@ -456,561 +600,606 @@ async function linkScope(params) {
456
600
  }
457
601
  }
458
602
  // 7. 逐个处理依赖(LINKED/RELINK/REPLACE/ABSORB/LINK_NEW)
459
- try {
460
- for (const item of classified) {
461
- const { dependency, status, localPath } = item;
462
- const libKey = registry.getLibraryKey(dependency.libName, dependency.commit);
463
- await store.ensureCompatibleStore(storePath, dependency.libName, dependency.commit);
464
- switch (status) {
465
- case DependencyStatus.LINKED: {
466
- const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
467
- const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
468
- const isLinkedGeneral = await resolveLibraryGeneralState(dependency);
469
- if (!isLinkedGeneral) {
470
- const supplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, { vars: configVars });
471
- await registerNestedLibraries(supplementResult.nestedLibraries, projectHash);
472
- if (supplementResult.downloaded.length > 0) {
473
- const linkedCommitPath = path.join(storePath, dependency.libName, dependency.commit);
474
- const { actualExisting: allExisting, satisfiedRequested } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
475
- clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
476
- tx.recordOp('link', localPath, linkedCommitPath);
477
- await linker.linkLib(localPath, linkedCommitPath, allExisting);
478
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, allExisting, projectHash, false);
479
- success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 已补充平台 [${supplementResult.downloaded.join(', ')}]`);
480
- }
603
+ for (const item of classified) {
604
+ const { dependency, status, localPath } = item;
605
+ await store.ensureCompatibleStore(storePath, dependency.libName, dependency.commit);
606
+ switch (status) {
607
+ case DependencyStatus.LINKED: {
608
+ const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
609
+ const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
610
+ const isLinkedGeneral = await resolveLibraryGeneralState(dependency, requestedPlatforms, configVars);
611
+ if (!isLinkedGeneral) {
612
+ const supplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, {
613
+ vars: configVars,
614
+ useGitLightweightDownload: gitLightweightDownload,
615
+ });
616
+ await registerNestedLibraries(supplementResult.nestedLibraries, projectHash);
617
+ if (supplementResult.downloaded.length > 0) {
618
+ const linkedCommitPath = path.join(storePath, dependency.libName, dependency.commit);
619
+ const { actualExisting: allExisting, satisfiedRequested } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
620
+ clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
621
+ tx.recordOp('link', localPath, linkedCommitPath);
622
+ await linker.linkLib(localPath, linkedCommitPath, allExisting);
623
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, allExisting, projectHash, false);
624
+ success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 已补充平台 [${supplementResult.downloaded.join(', ')}]`);
481
625
  }
482
- const { actualExisting: linkedExisting, satisfiedRequested } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
483
- clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
484
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, isLinkedGeneral ? [GENERAL_PLATFORM] : linkedExisting, projectHash, isLinkedGeneral);
485
- break;
486
626
  }
487
- case DependencyStatus.RELINK: {
488
- const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
489
- const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
490
- const relinkCommitPath = path.join(storePath, dependency.libName, dependency.commit);
491
- const isRelinkGeneral = await resolveLibraryGeneralState(dependency);
492
- if (isRelinkGeneral) {
493
- tx.recordOp('unlink', localPath);
494
- await linker.unlink(localPath);
495
- const sharedPath = path.join(relinkCommitPath, '_shared');
496
- tx.recordOp('link', localPath, sharedPath);
497
- await linker.linkGeneral(localPath, sharedPath);
498
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
499
- generalLibs.add(dependency.libName);
500
- success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,重建链接`);
501
- }
502
- else {
503
- const relinkSupplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, { vars: configVars });
504
- await registerNestedLibraries(relinkSupplementResult.nestedLibraries, projectHash);
505
- const { actualExisting: relinkExisting, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
506
- clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
507
- if (relinkExisting.length === 0) {
508
- const { KNOWN_PLATFORM_VALUES } = await import('../core/platform.js');
509
- const relinkCommitEntries = await fs.readdir(relinkCommitPath, { withFileTypes: true });
510
- const relinkAvailablePlatforms = relinkCommitEntries
511
- .filter(e => e.isDirectory() && e.name !== '_shared' && KNOWN_PLATFORM_VALUES.includes(e.name))
512
- .map(e => e.name);
513
- warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 已按规则跳过平台 [${platforms.join('/')}], 可用平台 [${relinkAvailablePlatforms.join(', ')}]`);
514
- const relinkLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
515
- const relinkLib = registry.getLibrary(relinkLibKey);
516
- if (relinkLib) {
517
- const unavailable = relinkLib.unavailablePlatforms || [];
518
- const missingRequested = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, relinkAvailablePlatforms, configVars).missingRequested;
519
- for (const requestedPlatform of missingRequested) {
520
- if (!unavailable.includes(requestedPlatform))
521
- unavailable.push(requestedPlatform);
522
- }
523
- registry.updateLibrary(relinkLibKey, { unavailablePlatforms: unavailable });
627
+ const { actualExisting: linkedExisting, satisfiedRequested } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
628
+ clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
629
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, isLinkedGeneral ? [GENERAL_PLATFORM] : linkedExisting, projectHash, isLinkedGeneral);
630
+ break;
631
+ }
632
+ case DependencyStatus.RELINK: {
633
+ const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
634
+ const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
635
+ const relinkCommitPath = path.join(storePath, dependency.libName, dependency.commit);
636
+ const isRelinkGeneral = await resolveLibraryGeneralState(dependency, requestedPlatforms, configVars);
637
+ if (isRelinkGeneral) {
638
+ tx.recordOp('unlink', localPath);
639
+ await linker.unlink(localPath);
640
+ const sharedPath = path.join(relinkCommitPath, '_shared');
641
+ tx.recordOp('link', localPath, sharedPath);
642
+ await linker.linkGeneral(localPath, sharedPath);
643
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
644
+ generalLibs.add(dependency.libName);
645
+ success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,重建链接`);
646
+ }
647
+ else {
648
+ const relinkSupplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, {
649
+ vars: configVars,
650
+ useGitLightweightDownload: gitLightweightDownload,
651
+ });
652
+ await registerNestedLibraries(relinkSupplementResult.nestedLibraries, projectHash);
653
+ const { actualExisting: relinkExisting, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
654
+ clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
655
+ if (relinkExisting.length === 0) {
656
+ const { KNOWN_PLATFORM_VALUES } = await import('../core/platform.js');
657
+ const relinkCommitEntries = await fs.readdir(relinkCommitPath, { withFileTypes: true });
658
+ const relinkAvailablePlatforms = relinkCommitEntries
659
+ .filter(e => e.isDirectory() && e.name !== '_shared' && KNOWN_PLATFORM_VALUES.includes(e.name))
660
+ .map(e => e.name);
661
+ warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 已按规则跳过平台 [${platforms.join('/')}], 可用平台 [${relinkAvailablePlatforms.join(', ')}]`);
662
+ const relinkLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
663
+ const relinkLib = registry.getLibrary(relinkLibKey);
664
+ if (relinkLib) {
665
+ const unavailable = relinkLib.unavailablePlatforms || [];
666
+ const missingRequested = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, relinkAvailablePlatforms, configVars).missingRequested;
667
+ for (const requestedPlatform of missingRequested) {
668
+ if (!unavailable.includes(requestedPlatform))
669
+ unavailable.push(requestedPlatform);
524
670
  }
525
- break;
526
- }
527
- tx.recordOp('unlink', localPath);
528
- await linker.unlink(localPath);
529
- tx.recordOp('link', localPath, relinkCommitPath);
530
- await linker.linkLib(localPath, relinkCommitPath, relinkExisting);
531
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, relinkExisting, projectHash, false);
532
- if (relinkSupplementResult.downloaded.length > 0) {
533
- success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 重建链接并补充平台 [${relinkExisting.join(', ')}]`);
534
- }
535
- else {
536
- success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 重建链接 [${relinkExisting.join(', ')}]`);
671
+ registry.updateLibrary(relinkLibKey, { unavailablePlatforms: unavailable });
537
672
  }
673
+ break;
538
674
  }
539
- break;
540
- }
541
- case DependencyStatus.REPLACE: {
542
- const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
543
- const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
544
- const replaceSize = await getDirSize(localPath);
545
- const replaceCommitPath = path.join(storePath, dependency.libName, dependency.commit);
546
- const isReplaceGeneral = await resolveLibraryGeneralState(dependency);
547
- if (isReplaceGeneral) {
548
- const sharedPath = path.join(replaceCommitPath, '_shared');
549
- tx.recordOp('replace', localPath, sharedPath);
550
- await linker.linkGeneral(localPath, sharedPath);
551
- savedBytes += replaceSize;
552
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
553
- generalLibs.add(dependency.libName);
554
- success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,创建链接`);
675
+ tx.recordOp('unlink', localPath);
676
+ await linker.unlink(localPath);
677
+ tx.recordOp('link', localPath, relinkCommitPath);
678
+ await linker.linkLib(localPath, relinkCommitPath, relinkExisting);
679
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, relinkExisting, projectHash, false);
680
+ if (relinkSupplementResult.downloaded.length > 0) {
681
+ success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 重建链接并补充平台 [${relinkExisting.join(', ')}]`);
555
682
  }
556
683
  else {
557
- const replaceSupplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, { vars: configVars });
558
- await registerNestedLibraries(replaceSupplementResult.nestedLibraries, projectHash);
559
- const { actualExisting: replaceExisting, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
560
- clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
561
- if (replaceExisting.length === 0) {
562
- const { KNOWN_PLATFORM_VALUES } = await import('../core/platform.js');
563
- const replaceCommitEntries = await fs.readdir(replaceCommitPath, { withFileTypes: true });
564
- const replaceAvailablePlatforms = replaceCommitEntries
565
- .filter(e => e.isDirectory() && e.name !== '_shared' && KNOWN_PLATFORM_VALUES.includes(e.name))
566
- .map(e => e.name);
567
- warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 已按规则跳过平台 [${platforms.join('/')}], 可用平台 [${replaceAvailablePlatforms.join(', ')}]`);
568
- const replaceLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
569
- const replaceLib = registry.getLibrary(replaceLibKey);
570
- if (replaceLib) {
571
- const unavailable = replaceLib.unavailablePlatforms || [];
572
- const missingRequested = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, replaceAvailablePlatforms, configVars).missingRequested;
573
- for (const requestedPlatform of missingRequested) {
574
- if (!unavailable.includes(requestedPlatform))
575
- unavailable.push(requestedPlatform);
576
- }
577
- registry.updateLibrary(replaceLibKey, { unavailablePlatforms: unavailable });
578
- }
579
- break;
580
- }
581
- tx.recordOp('replace', localPath, replaceCommitPath);
582
- await linker.linkLib(localPath, replaceCommitPath, replaceExisting);
583
- savedBytes += replaceSize;
584
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, replaceExisting, projectHash, false);
585
- if (replaceSupplementResult.downloaded.length > 0) {
586
- success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - Store 已有,创建链接并补充平台 [${replaceExisting.join(', ')}]`);
587
- }
588
- else {
589
- success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - Store 已有,创建链接 [${replaceExisting.join(', ')}]`);
590
- }
684
+ success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 重建链接 [${relinkExisting.join(', ')}]`);
591
685
  }
592
- break;
593
686
  }
594
- case DependencyStatus.ABSORB: {
595
- const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
596
- const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
597
- const storeCommitPath = path.join(storePath, dependency.libName, dependency.commit);
598
- const { KNOWN_PLATFORM_VALUES } = await import('../core/platform.js');
599
- const localDirEntries = await fs.readdir(localPath, { withFileTypes: true });
600
- const localPlatforms = localDirEntries
601
- .filter(entry => entry.isDirectory() && KNOWN_PLATFORM_VALUES.includes(entry.name))
602
- .map(entry => entry.name);
603
- const requestedTargets = [...new Set(requestedPlatforms.flatMap((platform) => getRequestedPlatformTargets(platform, dependency.sparse, configVars)))];
604
- const finalPlatforms = localPlatforms.filter((platform) => requestedTargets.includes(platform));
605
- if (finalPlatforms.length === 0 && localPlatforms.length > 0) {
606
- warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 已按规则跳过平台 [${platforms.join('/')}], 本地有 [${localPlatforms.join(', ')}]`);
607
- const absorbLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
608
- const absorbLib = registry.getLibrary(absorbLibKey);
609
- if (absorbLib) {
610
- const unavailable = absorbLib.unavailablePlatforms || [];
611
- const missingRequested = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, localPlatforms, configVars).missingRequested;
687
+ break;
688
+ }
689
+ case DependencyStatus.REPLACE: {
690
+ const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
691
+ const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
692
+ const replaceSize = await getDirSize(localPath);
693
+ const replaceCommitPath = path.join(storePath, dependency.libName, dependency.commit);
694
+ const isReplaceGeneral = await resolveLibraryGeneralState(dependency, requestedPlatforms, configVars);
695
+ if (isReplaceGeneral) {
696
+ const sharedPath = path.join(replaceCommitPath, '_shared');
697
+ tx.recordOp('replace', localPath, sharedPath);
698
+ await linker.linkGeneral(localPath, sharedPath);
699
+ savedBytes += replaceSize;
700
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
701
+ generalLibs.add(dependency.libName);
702
+ success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,创建链接`);
703
+ }
704
+ else {
705
+ const replaceSupplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, {
706
+ vars: configVars,
707
+ useGitLightweightDownload: gitLightweightDownload,
708
+ });
709
+ await registerNestedLibraries(replaceSupplementResult.nestedLibraries, projectHash);
710
+ const { actualExisting: replaceExisting, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
711
+ clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
712
+ if (replaceExisting.length === 0) {
713
+ const { KNOWN_PLATFORM_VALUES } = await import('../core/platform.js');
714
+ const replaceCommitEntries = await fs.readdir(replaceCommitPath, { withFileTypes: true });
715
+ const replaceAvailablePlatforms = replaceCommitEntries
716
+ .filter(e => e.isDirectory() && e.name !== '_shared' && KNOWN_PLATFORM_VALUES.includes(e.name))
717
+ .map(e => e.name);
718
+ warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 已按规则跳过平台 [${platforms.join('/')}], 可用平台 [${replaceAvailablePlatforms.join(', ')}]`);
719
+ const replaceLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
720
+ const replaceLib = registry.getLibrary(replaceLibKey);
721
+ if (replaceLib) {
722
+ const unavailable = replaceLib.unavailablePlatforms || [];
723
+ const missingRequested = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, replaceAvailablePlatforms, configVars).missingRequested;
612
724
  for (const requestedPlatform of missingRequested) {
613
725
  if (!unavailable.includes(requestedPlatform))
614
726
  unavailable.push(requestedPlatform);
615
727
  }
616
- registry.updateLibrary(absorbLibKey, { unavailablePlatforms: unavailable });
728
+ registry.updateLibrary(replaceLibKey, { unavailablePlatforms: unavailable });
617
729
  }
618
730
  break;
619
731
  }
620
- info(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 正在分析...`);
621
- const absorbSize = await getDirSize(localPath);
622
- const progressTracker = new ProgressTracker({
623
- name: ` 移入 Store`, total: absorbSize, showSpeed: true,
624
- });
625
- tx.recordOp('absorb', storeCommitPath, localPath);
626
- let progressStarted = false;
627
- const absorbResult = await store.absorbLib(localPath, finalPlatforms, dependency.libName, dependency.commit, {
628
- totalSize: absorbSize,
629
- onProgress: (copied, _total) => {
630
- if (!progressStarted) {
631
- progressStarted = true;
632
- progressTracker.start();
633
- }
634
- progressTracker.update(copied);
635
- },
636
- });
637
- if (progressStarted)
638
- progressTracker.stop();
639
- let absorbLinkPlatforms = [...Object.keys(absorbResult.platformPaths), ...absorbResult.skippedPlatforms];
732
+ tx.recordOp('replace', localPath, replaceCommitPath);
733
+ await linker.linkLib(localPath, replaceCommitPath, replaceExisting);
734
+ savedBytes += replaceSize;
735
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, replaceExisting, projectHash, false);
736
+ if (replaceSupplementResult.downloaded.length > 0) {
737
+ success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - Store 已有,创建链接并补充平台 [${replaceExisting.join(', ')}]`);
738
+ }
739
+ else {
740
+ success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - Store 已有,创建链接 [${replaceExisting.join(', ')}]`);
741
+ }
742
+ }
743
+ break;
744
+ }
745
+ case DependencyStatus.ABSORB: {
746
+ const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
747
+ const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
748
+ const storeCommitPath = path.join(storePath, dependency.libName, dependency.commit);
749
+ const { KNOWN_PLATFORM_VALUES } = await import('../core/platform.js');
750
+ const localDirEntries = await fs.readdir(localPath, { withFileTypes: true });
751
+ const localPlatforms = localDirEntries
752
+ .filter(entry => entry.isDirectory() && KNOWN_PLATFORM_VALUES.includes(entry.name))
753
+ .map(entry => entry.name);
754
+ const requestedTargets = [...new Set(requestedPlatforms.flatMap((platform) => getRequestedPlatformTargets(platform, dependency.sparse, configVars)))];
755
+ const finalPlatforms = localPlatforms.filter((platform) => requestedTargets.includes(platform));
756
+ if (finalPlatforms.length === 0 && localPlatforms.length > 0) {
757
+ warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 已按规则跳过平台 [${platforms.join('/')}], 本地有 [${localPlatforms.join(', ')}]`);
640
758
  const absorbLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
641
- if (!registry.getLibrary(absorbLibKey)) {
642
- registry.addLibrary({
643
- libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
644
- url: dependency.url, platforms: absorbLinkPlatforms, size: absorbSize,
645
- isGeneral: false,
646
- referencedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
647
- });
759
+ const absorbLib = registry.getLibrary(absorbLibKey);
760
+ if (absorbLib) {
761
+ const unavailable = absorbLib.unavailablePlatforms || [];
762
+ const missingRequested = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, localPlatforms, configVars).missingRequested;
763
+ for (const requestedPlatform of missingRequested) {
764
+ if (!unavailable.includes(requestedPlatform))
765
+ unavailable.push(requestedPlatform);
766
+ }
767
+ registry.updateLibrary(absorbLibKey, { unavailablePlatforms: unavailable });
648
768
  }
649
- if (absorbLinkPlatforms.length > 0) {
650
- for (const platform of absorbLinkPlatforms) {
651
- const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
652
- if (!registry.getStore(storeKey)) {
653
- const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, platform);
654
- registry.addStore({
655
- libName: dependency.libName, commit: dependency.commit, platform,
656
- branch: dependency.branch, url: dependency.url, ...integrity,
657
- usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
658
- });
659
- }
769
+ break;
770
+ }
771
+ info(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 正在分析...`);
772
+ const absorbSize = await getDirSize(localPath);
773
+ const progressTracker = new ProgressTracker({
774
+ name: ` 移入 Store`, total: absorbSize, showSpeed: true,
775
+ });
776
+ tx.recordOp('absorb', storeCommitPath, localPath);
777
+ let progressStarted = false;
778
+ const absorbResult = await store.absorbLib(localPath, finalPlatforms, dependency.libName, dependency.commit, {
779
+ totalSize: absorbSize,
780
+ onProgress: (copied, _total) => {
781
+ if (!progressStarted) {
782
+ progressStarted = true;
783
+ progressTracker.start();
660
784
  }
661
- await registerSharedStore(dependency.libName, dependency.commit, dependency.branch, dependency.url);
662
- await registerNestedLibraries(absorbResult.nestedLibraries, projectHash);
663
- const absorbSupplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, { vars: configVars });
664
- await registerNestedLibraries(absorbSupplementResult.nestedLibraries, projectHash);
665
- if (absorbSupplementResult.downloaded.length > 0) {
666
- absorbLinkPlatforms = [...absorbLinkPlatforms, ...absorbSupplementResult.downloaded];
785
+ progressTracker.update(copied);
786
+ },
787
+ });
788
+ if (progressStarted)
789
+ progressTracker.stop();
790
+ let absorbLinkPlatforms = [...Object.keys(absorbResult.platformPaths), ...absorbResult.skippedPlatforms];
791
+ const absorbLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
792
+ if (!registry.getLibrary(absorbLibKey)) {
793
+ registry.addLibrary({
794
+ libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
795
+ url: dependency.url, platforms: absorbLinkPlatforms, size: absorbSize,
796
+ isGeneral: false,
797
+ referencedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
798
+ });
799
+ }
800
+ if (absorbLinkPlatforms.length > 0) {
801
+ for (const platform of absorbLinkPlatforms) {
802
+ const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
803
+ if (!registry.getStore(storeKey)) {
804
+ const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, platform);
805
+ registry.addStore({
806
+ libName: dependency.libName, commit: dependency.commit, platform,
807
+ branch: dependency.branch, url: dependency.url, ...integrity,
808
+ usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
809
+ });
667
810
  }
668
- tx.recordOp('link', localPath, storeCommitPath);
669
- await linker.linkLib(localPath, storeCommitPath, absorbLinkPlatforms);
670
- for (const platform of absorbLinkPlatforms) {
671
- const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
672
- registry.addStoreReference(storeKey, projectHash);
811
+ }
812
+ await registerSharedStore(dependency.libName, dependency.commit, dependency.branch, dependency.url);
813
+ await registerNestedLibraries(absorbResult.nestedLibraries, projectHash);
814
+ const absorbSupplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, {
815
+ vars: configVars,
816
+ useGitLightweightDownload: gitLightweightDownload,
817
+ });
818
+ await registerNestedLibraries(absorbSupplementResult.nestedLibraries, projectHash);
819
+ if (absorbSupplementResult.downloaded.length > 0) {
820
+ absorbLinkPlatforms = [...absorbLinkPlatforms, ...absorbSupplementResult.downloaded];
821
+ }
822
+ tx.recordOp('link', localPath, storeCommitPath);
823
+ await linker.linkLib(localPath, storeCommitPath, absorbLinkPlatforms);
824
+ for (const platform of absorbLinkPlatforms) {
825
+ const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
826
+ registry.addStoreReference(storeKey, projectHash);
827
+ }
828
+ if (absorbSupplementResult.downloaded.length > 0) {
829
+ hint(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 本地已有,移入 Store 并补充平台 [${absorbLinkPlatforms.join(', ')}]`);
830
+ }
831
+ else {
832
+ hint(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 本地已有,移入 Store [${absorbLinkPlatforms.join(', ')}]`);
833
+ }
834
+ }
835
+ else {
836
+ const sharedPath = path.join(storeCommitPath, '_shared');
837
+ try {
838
+ await fs.access(sharedPath);
839
+ const sharedEntries = await fs.readdir(sharedPath);
840
+ if (sharedEntries.length === 0) {
841
+ warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - _shared 目录为空,请重新下载源文件后再 link`);
842
+ break;
673
843
  }
674
- if (absorbSupplementResult.downloaded.length > 0) {
675
- hint(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 本地已有,移入 Store 并补充平台 [${absorbLinkPlatforms.join(', ')}]`);
844
+ const { GENERAL_PLATFORM } = await import('../core/platform.js');
845
+ tx.recordOp('link', localPath, sharedPath);
846
+ await linker.linkGeneral(localPath, sharedPath);
847
+ const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, GENERAL_PLATFORM);
848
+ if (!registry.getStore(storeKey)) {
849
+ const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, GENERAL_PLATFORM);
850
+ registry.addStore({
851
+ libName: dependency.libName, commit: dependency.commit, platform: GENERAL_PLATFORM,
852
+ branch: dependency.branch, url: dependency.url, ...integrity,
853
+ usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
854
+ });
676
855
  }
677
- else {
678
- hint(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 本地已有,移入 Store [${absorbLinkPlatforms.join(', ')}]`);
856
+ registry.addStoreReference(storeKey, projectHash);
857
+ const libKeyGen = registry.getLibraryKey(dependency.libName, dependency.commit);
858
+ if (!registry.getLibrary(libKeyGen)) {
859
+ const sharedSize = await getDirSize(sharedPath);
860
+ registry.addLibrary({
861
+ libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
862
+ url: dependency.url, platforms: [GENERAL_PLATFORM], size: sharedSize,
863
+ isGeneral: true,
864
+ referencedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
865
+ });
679
866
  }
867
+ generalLibs.add(dependency.libName);
868
+ hint(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,整目录链接`);
680
869
  }
681
- else {
682
- const sharedPath = path.join(storeCommitPath, '_shared');
683
- try {
684
- await fs.access(sharedPath);
685
- const sharedEntries = await fs.readdir(sharedPath);
686
- if (sharedEntries.length === 0) {
687
- warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - _shared 目录为空,请重新下载源文件后再 link`);
688
- break;
689
- }
690
- const { GENERAL_PLATFORM } = await import('../core/platform.js');
691
- tx.recordOp('link', localPath, sharedPath);
692
- await linker.linkGeneral(localPath, sharedPath);
693
- const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, GENERAL_PLATFORM);
694
- if (!registry.getStore(storeKey)) {
695
- const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, GENERAL_PLATFORM);
696
- registry.addStore({
697
- libName: dependency.libName, commit: dependency.commit, platform: GENERAL_PLATFORM,
698
- branch: dependency.branch, url: dependency.url, ...integrity,
699
- usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
700
- });
701
- }
702
- registry.addStoreReference(storeKey, projectHash);
703
- const libKeyGen = registry.getLibraryKey(dependency.libName, dependency.commit);
704
- if (!registry.getLibrary(libKeyGen)) {
705
- const sharedSize = await getDirSize(sharedPath);
706
- registry.addLibrary({
707
- libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
708
- url: dependency.url, platforms: [GENERAL_PLATFORM], size: sharedSize,
709
- isGeneral: true,
710
- referencedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
711
- });
712
- }
713
- generalLibs.add(dependency.libName);
714
- hint(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,整目录链接`);
870
+ catch {
871
+ warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 本地目录不含任何内容,跳过`);
872
+ }
873
+ }
874
+ break;
875
+ }
876
+ case DependencyStatus.MISSING:
877
+ break;
878
+ case DependencyStatus.LINK_NEW: {
879
+ const linkNewCommitPath = path.join(storePath, dependency.libName, dependency.commit);
880
+ const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
881
+ const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
882
+ const { missingRequested: missing } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
883
+ const linkNewLibId = `${dependency.libName}@${dependency.commit}`;
884
+ if (missing.length > 0 && !skipAllDownloads && downloadConfirmedLibs.has(linkNewLibId)) {
885
+ info(`${dependency.libName} 缺少平台 [${missing.join(', ')}],开始下载...`);
886
+ const linkNewLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
887
+ const linkNewHistoryLib = registry.getLibrary(linkNewLibKey);
888
+ const linkNewMonitor = new DownloadMonitor({
889
+ name: ` ${dependency.libName}`, estimatedSize: linkNewHistoryLib?.size, getDirSize,
890
+ });
891
+ let downloadResult;
892
+ try {
893
+ downloadResult = await codepac.downloadToTemp({
894
+ url: dependency.url, commit: dependency.commit, branch: dependency.branch,
895
+ libName: dependency.libName, platforms: missing, sparse: dependency.sparse, vars: configVars,
896
+ useGitLightweightDownload: gitLightweightDownload,
897
+ onTempDirCreated: (_tempDir, libDir) => { linkNewMonitor.start(libDir); },
898
+ onHeartbeat: (message) => { linkNewMonitor.heartbeat(message); },
899
+ });
900
+ }
901
+ finally {
902
+ await linkNewMonitor.stop();
903
+ }
904
+ if (downloadResult.cleanedPlatforms.length > 0) {
905
+ hint(` 已过滤: ${downloadResult.cleanedPlatforms.join(', ')}`);
906
+ }
907
+ try {
908
+ const assessment = assessDownloadResult(missing, dependency.sparse, downloadResult, configVars);
909
+ clearSatisfiedAutoUnavailablePlatforms(registry, dependency, assessment.satisfiedRequested);
910
+ const filteredDownloaded = assessment.downloadedRequested;
911
+ if (filteredDownloaded.length > 0) {
912
+ tx.recordOp('absorb', linkNewCommitPath, downloadResult.libDir);
913
+ const linkNewAbsorbResult = await store.absorbLib(downloadResult.libDir, filteredDownloaded, dependency.libName, dependency.commit);
914
+ await registerNestedLibraries(linkNewAbsorbResult.nestedLibraries, projectHash);
715
915
  }
716
- catch {
717
- warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 本地目录不含任何内容,跳过`);
916
+ }
917
+ finally {
918
+ await fs.rm(downloadResult.tempDir, { recursive: true, force: true }).catch(() => { });
919
+ }
920
+ }
921
+ const { actualExisting: linkNewExisting, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
922
+ clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
923
+ const isLinkNewGeneral = await resolveLibraryGeneralState(dependency, requestedPlatforms, configVars);
924
+ if (isLinkNewGeneral) {
925
+ const sharedPath = path.join(linkNewCommitPath, '_shared');
926
+ tx.recordOp('link', localPath, sharedPath);
927
+ await linker.linkGeneral(localPath, sharedPath);
928
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
929
+ generalLibs.add(dependency.libName);
930
+ success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,创建链接`);
931
+ }
932
+ else if (linkNewExisting.length === 0) {
933
+ const { KNOWN_PLATFORM_VALUES } = await import('../core/platform.js');
934
+ const commitEntries = await fs.readdir(linkNewCommitPath, { withFileTypes: true });
935
+ const availablePlatforms = commitEntries
936
+ .filter(e => e.isDirectory() && e.name !== '_shared' && KNOWN_PLATFORM_VALUES.includes(e.name))
937
+ .map(e => e.name);
938
+ warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 不支持 ${platforms.join('/')} 平台 [可用: ${availablePlatforms.join(', ')}]`);
939
+ const lnLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
940
+ const lnLib = registry.getLibrary(lnLibKey);
941
+ if (lnLib) {
942
+ const unavailable = lnLib.unavailablePlatforms || [];
943
+ const missingRequested = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, availablePlatforms, configVars).missingRequested;
944
+ for (const requestedPlatform of missingRequested) {
945
+ if (!unavailable.includes(requestedPlatform))
946
+ unavailable.push(requestedPlatform);
718
947
  }
948
+ registry.updateLibrary(lnLibKey, { unavailablePlatforms: unavailable });
719
949
  }
720
950
  break;
721
951
  }
722
- case DependencyStatus.MISSING:
723
- break;
724
- case DependencyStatus.LINK_NEW: {
725
- const linkNewCommitPath = path.join(storePath, dependency.libName, dependency.commit);
952
+ else {
953
+ tx.recordOp('link', localPath, linkNewCommitPath);
954
+ await linker.linkLib(localPath, linkNewCommitPath, linkNewExisting);
955
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, linkNewExisting, projectHash, false);
956
+ success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 创建链接 [${linkNewExisting.join(', ')}]`);
957
+ }
958
+ break;
959
+ }
960
+ }
961
+ // 无论当前是跳过、补平台还是重建链接,都要把本地残留的平台目录清理到最终平台集合
962
+ const desiredLocalPlatforms = await resolveStoredPlatforms(dependency.libName, dependency.commit, finalLinkPlatforms);
963
+ await linker.cleanupLocalExtraPlatforms(localPath, desiredLocalPlatforms);
964
+ await tx.save();
965
+ }
966
+ // 8. 并行处理 MISSING 依赖
967
+ const missingItems = classified.filter((c) => c.status === DependencyStatus.MISSING);
968
+ if (missingItems.length > 0 && download && !skipAllDownloads) {
969
+ const toDownload = missingItems.filter((item) => {
970
+ const libId = `${item.dependency.libName}@${item.dependency.commit}`;
971
+ return downloadConfirmedLibs.has(libId);
972
+ });
973
+ if (toDownload.length > 0) {
974
+ info(`开始并行下载 ${toDownload.length} 个库 (最多 ${concurrency} 个并发)...`);
975
+ blank();
976
+ const isTTY = process.stdout.isTTY ?? false;
977
+ const multiBarManager = isTTY ? new MultiBarManager() : null;
978
+ const downloadLimit = pLimit(concurrency);
979
+ const downloadTasks = toDownload.map((item) => downloadLimit(async () => {
980
+ const { dependency, localPath } = item;
981
+ const pLog = {
982
+ info: (msg) => multiBarManager ? multiBarManager.log(`[info] ${msg}`) : info(msg),
983
+ success: (msg) => multiBarManager ? multiBarManager.log(`[ok] ${msg}`) : success(msg),
984
+ hint: (msg) => multiBarManager ? multiBarManager.log(`[hint] ${msg}`) : hint(msg),
985
+ warn: (msg) => multiBarManager ? multiBarManager.log(`[warn] ${msg}`) : warn(msg),
986
+ error: (msg) => multiBarManager ? multiBarManager.log(`[error] ${msg}`) : error(msg),
987
+ };
988
+ try {
989
+ const storeCommitPath = path.join(storePath, dependency.libName, dependency.commit);
726
990
  const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
727
991
  const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
728
- const { missingRequested: missing } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
729
- const linkNewLibId = `${dependency.libName}@${dependency.commit}`;
730
- if (missing.length > 0 && !skipAllDownloads && downloadConfirmedLibs.has(linkNewLibId)) {
731
- info(`${dependency.libName} 缺少平台 [${missing.join(', ')}],开始下载...`);
732
- const linkNewLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
733
- const linkNewHistoryLib = registry.getLibrary(linkNewLibKey);
734
- const linkNewMonitor = new DownloadMonitor({
735
- name: ` ${dependency.libName}`, estimatedSize: linkNewHistoryLib?.size, getDirSize,
736
- });
737
- let downloadResult;
738
- try {
739
- downloadResult = await codepac.downloadToTemp({
740
- url: dependency.url, commit: dependency.commit, branch: dependency.branch,
741
- libName: dependency.libName, platforms: missing, sparse: dependency.sparse, vars: configVars,
742
- onTempDirCreated: (_tempDir, libDir) => { linkNewMonitor.start(libDir); },
743
- onHeartbeat: (message) => { linkNewMonitor.heartbeat(message); },
744
- });
745
- }
746
- finally {
747
- await linkNewMonitor.stop();
748
- }
749
- if (downloadResult.cleanedPlatforms.length > 0) {
750
- hint(` 已过滤: ${downloadResult.cleanedPlatforms.join(', ')}`);
751
- }
752
- try {
753
- const assessment = assessDownloadResult(missing, dependency.sparse, downloadResult, configVars);
754
- clearSatisfiedAutoUnavailablePlatforms(registry, dependency, assessment.satisfiedRequested);
755
- const filteredDownloaded = assessment.downloadedRequested;
756
- if (filteredDownloaded.length > 0) {
757
- tx.recordOp('absorb', linkNewCommitPath, downloadResult.libDir);
758
- const linkNewAbsorbResult = await store.absorbLib(downloadResult.libDir, filteredDownloaded, dependency.libName, dependency.commit);
759
- await registerNestedLibraries(linkNewAbsorbResult.nestedLibraries, projectHash);
760
- }
761
- }
762
- finally {
763
- await fs.rm(downloadResult.tempDir, { recursive: true, force: true }).catch(() => { });
992
+ if (requestedPlatforms.length === 0) {
993
+ pLog.warn(`${dependency.libName} 请求平台 [${platforms.join(', ')}] 均已按规则跳过,没有可链接产物`);
994
+ if (await linker.cleanupEmptyLocalDirectory(localPath)) {
995
+ pLog.warn(`${dependency.libName} 已清理历史遗留的空本地目录`);
764
996
  }
997
+ return {
998
+ success: false,
999
+ name: dependency.libName,
1000
+ skipped: true,
1001
+ skippedPlatforms: blockedPlatforms,
1002
+ unsupported: true,
1003
+ };
765
1004
  }
766
- const { actualExisting: linkNewExisting, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
1005
+ const { actualExisting: existing, missingRequested: missing, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
767
1006
  clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
768
- const isLinkNewGeneral = await resolveLibraryGeneralState(dependency);
769
- if (isLinkNewGeneral) {
770
- const sharedPath = path.join(linkNewCommitPath, '_shared');
771
- tx.recordOp('link', localPath, sharedPath);
772
- await linker.linkGeneral(localPath, sharedPath);
773
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
774
- generalLibs.add(dependency.libName);
775
- success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,创建链接`);
776
- }
777
- else if (linkNewExisting.length === 0) {
778
- const { KNOWN_PLATFORM_VALUES } = await import('../core/platform.js');
779
- const commitEntries = await fs.readdir(linkNewCommitPath, { withFileTypes: true });
780
- const availablePlatforms = commitEntries
781
- .filter(e => e.isDirectory() && e.name !== '_shared' && KNOWN_PLATFORM_VALUES.includes(e.name))
782
- .map(e => e.name);
783
- warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 不支持 ${platforms.join('/')} 平台 [可用: ${availablePlatforms.join(', ')}]`);
784
- const lnLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
785
- const lnLib = registry.getLibrary(lnLibKey);
786
- if (lnLib) {
787
- const unavailable = lnLib.unavailablePlatforms || [];
788
- const missingRequested = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, availablePlatforms, configVars).missingRequested;
789
- for (const requestedPlatform of missingRequested) {
790
- if (!unavailable.includes(requestedPlatform))
791
- unavailable.push(requestedPlatform);
1007
+ if (missing.length === 0) {
1008
+ if (existing.length === 0) {
1009
+ pLog.warn(`${dependency.libName} 请求平台 [${requestedPlatforms.join(', ')}] 在 Store 中没有可链接产物,已拒绝创建空目录`);
1010
+ if (await linker.cleanupEmptyLocalDirectory(localPath)) {
1011
+ pLog.warn(`${dependency.libName} 已清理历史遗留的空本地目录`);
792
1012
  }
793
- registry.updateLibrary(lnLibKey, { unavailablePlatforms: unavailable });
1013
+ return {
1014
+ success: false,
1015
+ name: dependency.libName,
1016
+ skipped: true,
1017
+ skippedPlatforms: requestedPlatforms,
1018
+ };
794
1019
  }
795
- break;
796
- }
797
- else {
798
- tx.recordOp('link', localPath, linkNewCommitPath);
799
- await linker.linkLib(localPath, linkNewCommitPath, linkNewExisting);
800
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, linkNewExisting, projectHash, false);
801
- success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 创建链接 [${linkNewExisting.join(', ')}]`);
1020
+ pLog.info(`${dependency.libName} 所有平台已存在,直接链接...`);
1021
+ tx.recordOp('link', localPath, storeCommitPath);
1022
+ await linker.linkLib(localPath, storeCommitPath, existing);
1023
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, existing, projectHash, false);
1024
+ pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 链接完成 [${existing.join(', ')}]`);
1025
+ return { success: true, name: dependency.libName, downloadedPlatforms: existing, skippedPlatforms: [] };
802
1026
  }
803
- break;
804
- }
805
- }
806
- // 无论当前是跳过、补平台还是重建链接,都要把本地残留的平台目录清理到最终平台集合
807
- const desiredLocalPlatforms = await resolveStoredPlatforms(dependency.libName, dependency.commit, finalLinkPlatforms);
808
- await linker.cleanupLocalExtraPlatforms(localPath, desiredLocalPlatforms);
809
- await tx.save();
810
- }
811
- // 8. 并行处理 MISSING 依赖
812
- const missingItems = classified.filter((c) => c.status === DependencyStatus.MISSING);
813
- if (missingItems.length > 0 && download && !skipAllDownloads) {
814
- const toDownload = missingItems.filter((item) => {
815
- const libId = `${item.dependency.libName}@${item.dependency.commit}`;
816
- return downloadConfirmedLibs.has(libId);
817
- });
818
- if (toDownload.length > 0) {
819
- info(`开始并行下载 ${toDownload.length} 个库 (最多 ${concurrency} 个并发)...`);
820
- blank();
821
- const isTTY = process.stdout.isTTY ?? false;
822
- const multiBarManager = isTTY ? new MultiBarManager() : null;
823
- const downloadLimit = pLimit(concurrency);
824
- const downloadTasks = toDownload.map((item) => downloadLimit(async () => {
825
- const { dependency, localPath } = item;
826
- const pLog = {
827
- info: (msg) => multiBarManager ? multiBarManager.log(`[info] ${msg}`) : info(msg),
828
- success: (msg) => multiBarManager ? multiBarManager.log(`[ok] ${msg}`) : success(msg),
829
- hint: (msg) => multiBarManager ? multiBarManager.log(`[hint] ${msg}`) : hint(msg),
830
- warn: (msg) => multiBarManager ? multiBarManager.log(`[warn] ${msg}`) : warn(msg),
831
- error: (msg) => multiBarManager ? multiBarManager.log(`[error] ${msg}`) : error(msg),
832
- };
833
- try {
834
- const storeCommitPath = path.join(storePath, dependency.libName, dependency.commit);
835
- const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
836
- const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
837
- const { actualExisting: existing, missingRequested: missing, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
838
- clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
839
- if (missing.length === 0) {
840
- pLog.info(`${dependency.libName} 所有平台已存在,直接链接...`);
841
- tx.recordOp('link', localPath, storeCommitPath);
842
- await linker.linkLib(localPath, storeCommitPath, existing);
843
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, existing, projectHash, false);
844
- pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 链接完成 [${existing.join(', ')}]`);
845
- return { success: true, name: dependency.libName, downloadedPlatforms: existing, skippedPlatforms: [] };
1027
+ const dlLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
1028
+ const historyLib = registry.getLibrary(dlLibKey);
1029
+ const unavailablePlatforms = blockedPlatforms;
1030
+ const dlToDownload = missing.filter(p => !unavailablePlatforms.includes(p));
1031
+ const knownUnavailable = missing.filter(p => unavailablePlatforms.includes(p));
1032
+ if (dlToDownload.length === 0) {
1033
+ if (knownUnavailable.length > 0) {
1034
+ pLog.warn(`${dependency.libName} 平台 [${knownUnavailable.join(', ')}] 已按规则跳过`);
846
1035
  }
847
- const dlLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
848
- const historyLib = registry.getLibrary(dlLibKey);
849
- const unavailablePlatforms = blockedPlatforms;
850
- const dlToDownload = missing.filter(p => !unavailablePlatforms.includes(p));
851
- const knownUnavailable = missing.filter(p => unavailablePlatforms.includes(p));
852
- if (dlToDownload.length === 0) {
853
- if (knownUnavailable.length > 0) {
854
- pLog.warn(`${dependency.libName} 平台 [${knownUnavailable.join(', ')}] 已按规则跳过`);
855
- }
856
- return { success: false, name: dependency.libName, skipped: true, skippedPlatforms: missing, unsupported: true };
1036
+ if (await linker.cleanupEmptyLocalDirectory(localPath)) {
1037
+ pLog.warn(`${dependency.libName} 已清理历史遗留的空本地目录`);
857
1038
  }
858
- pLog.info(`下载 ${dependency.libName} [${dlToDownload.join(', ')}]...`);
859
- const estimatedSize = historyLib?.size;
860
- const downloadMonitor = new DownloadMonitor({
861
- name: ` ${dependency.libName}`, estimatedSize, getDirSize,
862
- manager: multiBarManager ?? undefined,
1039
+ return { success: false, name: dependency.libName, skipped: true, skippedPlatforms: missing, unsupported: true };
1040
+ }
1041
+ pLog.info(`下载 ${dependency.libName} [${dlToDownload.join(', ')}]...`);
1042
+ const estimatedSize = historyLib?.size;
1043
+ const downloadMonitor = new DownloadMonitor({
1044
+ name: ` ${dependency.libName}`, estimatedSize, getDirSize,
1045
+ manager: multiBarManager ?? undefined,
1046
+ });
1047
+ let downloadResult;
1048
+ try {
1049
+ downloadResult = await codepac.downloadToTemp({
1050
+ url: dependency.url, commit: dependency.commit, branch: dependency.branch,
1051
+ libName: dependency.libName, platforms: dlToDownload, sparse: dependency.sparse,
1052
+ vars: configVars,
1053
+ useGitLightweightDownload: gitLightweightDownload,
1054
+ onTempDirCreated: (_tempDir, libDir) => { downloadMonitor.start(libDir); },
1055
+ onHeartbeat: (message) => { downloadMonitor.heartbeat(message); },
863
1056
  });
864
- let downloadResult;
865
- try {
866
- downloadResult = await codepac.downloadToTemp({
867
- url: dependency.url, commit: dependency.commit, branch: dependency.branch,
868
- libName: dependency.libName, platforms: dlToDownload, sparse: dependency.sparse,
869
- vars: configVars,
870
- onTempDirCreated: (_tempDir, libDir) => { downloadMonitor.start(libDir); },
871
- onHeartbeat: (message) => { downloadMonitor.heartbeat(message); },
1057
+ }
1058
+ finally {
1059
+ await downloadMonitor.stop();
1060
+ }
1061
+ if (downloadResult.cleanedPlatforms.length > 0) {
1062
+ pLog.hint(` 已过滤: ${downloadResult.cleanedPlatforms.join(', ')}`);
1063
+ }
1064
+ try {
1065
+ const assessment = assessDownloadResult(dlToDownload, dependency.sparse, downloadResult, configVars);
1066
+ clearSatisfiedAutoUnavailablePlatforms(registry, dependency, assessment.satisfiedRequested);
1067
+ if (assessment.isPureGeneral) {
1068
+ upsertLibraryAvailability(dependency, {
1069
+ platforms: [GENERAL_PLATFORM],
1070
+ isGeneral: true,
872
1071
  });
1072
+ tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
1073
+ await store.absorbGeneral(downloadResult.libDir, dependency.libName, dependency.commit);
1074
+ const sharedPath = path.join(storeCommitPath, '_shared');
1075
+ tx.recordOp('link', localPath, sharedPath);
1076
+ await linker.linkGeneral(localPath, sharedPath);
1077
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
1078
+ generalLibs.add(dependency.libName);
1079
+ pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,下载完成`);
1080
+ return { success: true, name: dependency.libName, downloadedPlatforms: [GENERAL_PLATFORM], skippedPlatforms: [], isGeneral: true };
873
1081
  }
874
- finally {
875
- await downloadMonitor.stop();
1082
+ const filteredDownloaded = assessment.downloadedRequested;
1083
+ const newUnavailable = assessment.unavailableRequested;
1084
+ if (newUnavailable.length > 0) {
1085
+ const updatedUnavailable = [...new Set([...unavailablePlatforms, ...newUnavailable])];
1086
+ upsertLibraryAvailability(dependency, {
1087
+ unavailablePlatforms: updatedUnavailable,
1088
+ isGeneral: false,
1089
+ });
1090
+ pLog.warn(`${dependency.libName} 平台 [${newUnavailable.join(', ')}] 远程不存在,已记录`);
876
1091
  }
877
- if (downloadResult.cleanedPlatforms.length > 0) {
878
- pLog.hint(` 已过滤: ${downloadResult.cleanedPlatforms.join(', ')}`);
1092
+ if (filteredDownloaded.length > 0) {
1093
+ tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
1094
+ const downloadAbsorbResult = await store.absorbLib(downloadResult.libDir, filteredDownloaded, dependency.libName, dependency.commit);
1095
+ await registerNestedLibraries(downloadAbsorbResult.nestedLibraries, projectHash);
879
1096
  }
880
- try {
881
- const assessment = assessDownloadResult(dlToDownload, dependency.sparse, downloadResult, configVars);
882
- clearSatisfiedAutoUnavailablePlatforms(registry, dependency, assessment.satisfiedRequested);
883
- if (assessment.isPureGeneral) {
884
- upsertLibraryAvailability(dependency, {
885
- platforms: [GENERAL_PLATFORM],
886
- isGeneral: true,
887
- });
888
- tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
889
- await store.absorbGeneral(downloadResult.libDir, dependency.libName, dependency.commit);
890
- const sharedPath = path.join(storeCommitPath, '_shared');
891
- tx.recordOp('link', localPath, sharedPath);
892
- await linker.linkGeneral(localPath, sharedPath);
893
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
894
- generalLibs.add(dependency.libName);
895
- pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,下载完成`);
896
- return { success: true, name: dependency.libName, downloadedPlatforms: [GENERAL_PLATFORM], skippedPlatforms: [], isGeneral: true };
897
- }
898
- const filteredDownloaded = assessment.downloadedRequested;
899
- const newUnavailable = assessment.unavailableRequested;
900
- if (newUnavailable.length > 0) {
901
- const updatedUnavailable = [...new Set([...unavailablePlatforms, ...newUnavailable])];
902
- upsertLibraryAvailability(dependency, {
903
- unavailablePlatforms: updatedUnavailable,
904
- isGeneral: false,
905
- });
906
- pLog.warn(`${dependency.libName} 平台 [${newUnavailable.join(', ')}] 远程不存在,已记录`);
1097
+ const linkPlatforms = [...existing, ...filteredDownloaded];
1098
+ if (linkPlatforms.length === 0) {
1099
+ pLog.warn(`${dependency.libName} 下载后没有生成请求平台 [${requestedPlatforms.join(', ')}] 的可链接产物,已拒绝创建空目录`);
1100
+ if (await linker.cleanupEmptyLocalDirectory(localPath)) {
1101
+ pLog.warn(`${dependency.libName} 已清理历史遗留的空本地目录`);
907
1102
  }
908
- if (filteredDownloaded.length > 0) {
909
- tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
910
- const downloadAbsorbResult = await store.absorbLib(downloadResult.libDir, filteredDownloaded, dependency.libName, dependency.commit);
911
- await registerNestedLibraries(downloadAbsorbResult.nestedLibraries, projectHash);
912
- }
913
- const linkPlatforms = [...existing, ...filteredDownloaded];
914
- if (linkPlatforms.length === 0) {
915
- return {
916
- success: false,
917
- name: dependency.libName,
918
- skipped: true,
919
- skippedPlatforms: requestedPlatforms,
920
- };
921
- }
922
- tx.recordOp('link', localPath, storeCommitPath);
923
- await linker.linkLib(localPath, storeCommitPath, linkPlatforms);
924
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, linkPlatforms, projectHash, false);
925
- const notLinkedPlatforms = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, linkPlatforms, configVars).missingRequested;
926
- pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 下载完成 [${linkPlatforms.join(', ')}]`);
927
- return { success: true, name: dependency.libName, downloadedPlatforms: linkPlatforms, skippedPlatforms: notLinkedPlatforms };
928
- }
929
- finally {
930
- await fs.rm(downloadResult.tempDir, { recursive: true, force: true }).catch(() => { });
1103
+ return {
1104
+ success: false,
1105
+ name: dependency.libName,
1106
+ skipped: true,
1107
+ skippedPlatforms: requestedPlatforms,
1108
+ };
931
1109
  }
1110
+ tx.recordOp('link', localPath, storeCommitPath);
1111
+ await linker.linkLib(localPath, storeCommitPath, linkPlatforms);
1112
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, linkPlatforms, projectHash, false);
1113
+ const notLinkedPlatforms = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, linkPlatforms, configVars).missingRequested;
1114
+ pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 下载完成 [${linkPlatforms.join(', ')}]`);
1115
+ return { success: true, name: dependency.libName, downloadedPlatforms: linkPlatforms, skippedPlatforms: notLinkedPlatforms };
932
1116
  }
933
- catch (err) {
934
- pLog.error(`${dependency.libName} 下载失败: ${err.message}`);
935
- return { success: false, name: dependency.libName, error: err.message };
936
- }
937
- }));
938
- const results = await Promise.all(downloadTasks);
939
- multiBarManager?.stop();
940
- const succeeded = results.filter((r) => r.success);
941
- const failed = results.filter((r) => !r.success && !('skipped' in r && r.skipped));
942
- blank();
943
- info(`下载完成: ${succeeded.length}/${toDownload.length} 个库`);
944
- if (failed.length > 0)
945
- warn(`${failed.length} 个库下载失败`);
946
- const allSkipped = [];
947
- for (const r of results) {
948
- if ('skippedPlatforms' in r && r.skippedPlatforms && r.skippedPlatforms.length > 0) {
949
- allSkipped.push({ name: r.name, platforms: r.skippedPlatforms });
1117
+ finally {
1118
+ await fs.rm(downloadResult.tempDir, { recursive: true, force: true }).catch(() => { });
950
1119
  }
951
1120
  }
952
- if (allSkipped.length > 0) {
953
- blank();
954
- warn('以下库/平台组合不可用(已跳过):');
955
- for (const item of allSkipped) {
956
- warn(` - ${item.name} / ${item.platforms.join(', ')}`);
957
- }
1121
+ catch (err) {
1122
+ pLog.error(`${dependency.libName} 下载失败: ${err.message}`);
1123
+ return { success: false, name: dependency.libName, error: err.message };
1124
+ }
1125
+ }));
1126
+ const results = await Promise.all(downloadTasks);
1127
+ multiBarManager?.stop();
1128
+ const succeeded = results.filter((r) => r.success);
1129
+ const failed = results.filter((r) => !r.success && !('skipped' in r && r.skipped));
1130
+ blank();
1131
+ info(`下载完成: ${succeeded.length}/${toDownload.length} 个库`);
1132
+ if (failed.length > 0)
1133
+ warn(`${failed.length} 个库下载失败`);
1134
+ const allSkipped = [];
1135
+ for (const r of results) {
1136
+ if ('skippedPlatforms' in r && r.skippedPlatforms && r.skippedPlatforms.length > 0) {
1137
+ allSkipped.push({ name: r.name, platforms: r.skippedPlatforms });
958
1138
  }
959
- for (const r of succeeded)
960
- downloadedLibs.push(r.name);
961
1139
  }
962
- }
963
- else if (missingItems.length > 0 && !download) {
964
- for (const item of missingItems) {
965
- warn(`${item.dependency.libName} (${item.dependency.commit.slice(0, 7)}) - 缺失 (跳过下载)`);
1140
+ if (allSkipped.length > 0) {
1141
+ blank();
1142
+ warn('以下库/平台组合不可用(已跳过):');
1143
+ for (const item of allSkipped) {
1144
+ warn(` - ${item.name} / ${item.platforms.join(', ')}`);
1145
+ }
966
1146
  }
1147
+ for (const r of succeeded)
1148
+ downloadedLibs.push(r.name);
967
1149
  }
968
- // 9. 处理嵌套依赖 (actions)
969
- const topLevelConfig = await parseCodepacDep(configPath);
970
- const actions = extractActions(topLevelConfig);
971
- const nestedLinkedDeps = [];
972
- if (actions.length > 0) {
973
- blank();
974
- separator();
975
- info(`发现 ${actions.length} 个嵌套依赖配置`);
976
- const nestedContext = {
977
- depth: 0, processedConfigs: new Set([configPath]), platforms, vars: configVars,
978
- };
979
- const thirdPartyDir = path.dirname(configPath);
980
- for (const action of actions) {
981
- await processAction(action, nestedContext, thirdPartyDir, {
982
- tx, registry, projectHash, projectRoot, dryRun, download, yes,
983
- generalLibs, downloadedLibs, nestedLinkedDeps,
984
- });
985
- }
1150
+ }
1151
+ else if (missingItems.length > 0 && !download) {
1152
+ for (const item of missingItems) {
1153
+ warn(`${item.dependency.libName} (${item.dependency.commit.slice(0, 7)}) - 缺失 (跳过下载)`);
986
1154
  }
987
- // 10. 同步 cache 文件
988
- await syncCacheFile(configPath);
989
- // 11. 构建返回结果
990
- const linkedDeps = [];
991
- for (const item of classified) {
992
- if (item.status === DependencyStatus.MISSING && !downloadedLibs.includes(item.dependency.libName)) {
993
- continue;
994
- }
995
- const actualPlatforms = generalLibs.has(item.dependency.libName)
996
- ? [GENERAL_PLATFORM]
997
- : await resolveStoredPlatforms(item.dependency.libName, item.dependency.commit, platforms);
998
- linkedDeps.push({
999
- libName: item.dependency.libName,
1000
- commit: item.dependency.commit,
1001
- platform: actualPlatforms[0] ?? platforms[0],
1002
- linkedPath: path.relative(projectRoot, item.localPath),
1003
- scope,
1155
+ }
1156
+ // 9. 处理嵌套依赖 (actions)
1157
+ const actions = selectedActions;
1158
+ const nestedLinkedDeps = [];
1159
+ if (actions.length > 0) {
1160
+ blank();
1161
+ separator();
1162
+ info(`发现 ${actions.length} 个嵌套依赖配置`);
1163
+ const nestedContext = {
1164
+ depth: 0,
1165
+ processedConfigs: new Set([buildActionVisitKey(configPath, path.dirname(configPath))]),
1166
+ platforms,
1167
+ codepacPlatforms,
1168
+ vars: configVars,
1169
+ };
1170
+ const thirdPartyDir = path.dirname(configPath);
1171
+ for (const action of actions) {
1172
+ await processAction(action, nestedContext, configPath, thirdPartyDir, {
1173
+ tx, registry, projectHash, projectRoot, dryRun, download, yes,
1174
+ gitLightweightDownload, skipActions: new Set(params.skipActions ?? []),
1175
+ generalLibs, downloadedLibs, nestedLinkedDeps,
1004
1176
  });
1005
1177
  }
1006
- return {
1007
- linkedDeps, nestedLinkedDeps, generalLibs, downloadedLibs, savedBytes, finalLinkPlatforms, stats,
1008
- };
1009
1178
  }
1010
- catch (err) {
1011
- // 将异常传播给调用者(linkProject try-catch 会处理回滚)
1012
- throw err;
1179
+ // 10. 同步 cache 文件
1180
+ for (const cacheConfigPath of cacheConfigPaths) {
1181
+ await syncCacheFile(cacheConfigPath);
1182
+ }
1183
+ // 11. 构建返回结果
1184
+ const linkedDeps = [];
1185
+ for (const item of classified) {
1186
+ if (item.status === DependencyStatus.MISSING && !downloadedLibs.includes(item.dependency.libName)) {
1187
+ continue;
1188
+ }
1189
+ const actualPlatforms = generalLibs.has(item.dependency.libName)
1190
+ ? [GENERAL_PLATFORM]
1191
+ : await resolveStoredPlatforms(item.dependency.libName, item.dependency.commit, platforms);
1192
+ linkedDeps.push({
1193
+ libName: item.dependency.libName,
1194
+ commit: item.dependency.commit,
1195
+ platform: actualPlatforms[0] ?? platforms[0],
1196
+ linkedPath: path.relative(projectRoot, item.localPath),
1197
+ scope,
1198
+ });
1013
1199
  }
1200
+ return {
1201
+ linkedDeps, nestedLinkedDeps, generalLibs, downloadedLibs, savedBytes, finalLinkPlatforms, stats,
1202
+ };
1014
1203
  }
1015
1204
  /**
1016
1205
  * 执行链接操作
@@ -1024,6 +1213,7 @@ export async function linkProject(projectPath, options) {
1024
1213
  if (cfg?.proxy)
1025
1214
  setProxyConfig(cfg.proxy);
1026
1215
  const concurrency = cfg?.concurrency ?? 5;
1216
+ const gitLightweightDownload = cfg?.gitLightweightDownload ?? true;
1027
1217
  const registry = getRegistry();
1028
1218
  await registry.load();
1029
1219
  const existingProject = registry.getProjectByPath(normalizedPath);
@@ -1062,9 +1252,10 @@ export async function linkProject(projectPath, options) {
1062
1252
  error(`路径不存在: ${absolutePath}`);
1063
1253
  process.exit(EXIT_CODES.NOINPUT);
1064
1254
  }
1065
- // 发现可选配置文件
1066
- const thirdpartyDir = path.join(normalizedPath, '3rdparty');
1067
- const configDiscovery = await findAllCodepacConfigs(thirdpartyDir);
1255
+ // 发现可选配置文件,扫描位置跟随实际主配置所在目录。
1256
+ const configDiscovery = discoveredConfigPath
1257
+ ? await findAllCodepacConfigs(path.dirname(discoveredConfigPath))
1258
+ : null;
1068
1259
  let selectedOptionalConfigs = [];
1069
1260
  if (configDiscovery && configDiscovery.optionalConfigs.length > 0) {
1070
1261
  if (options.config && options.config.length > 0) {
@@ -1111,14 +1302,21 @@ export async function linkProject(projectPath, options) {
1111
1302
  // === 阶段 2: Submodule 检测 ===
1112
1303
  let selectedSubmodules = [];
1113
1304
  if (options.submodules !== false) {
1114
- const submoduleConfigs = await findSubmoduleConfigs(normalizedPath);
1305
+ const codepacPlatforms = [...new Set(platforms.map((platform) => getBaseKeyForCodepac(platform)))];
1306
+ const submoduleConfigs = await findSubmoduleConfigs(normalizedPath, '', 0, codepacPlatforms);
1115
1307
  if (submoduleConfigs.length > 0) {
1116
1308
  selectedSubmodules = await selectSubmodules(submoduleConfigs, options, existingProject?.submodules);
1117
1309
  // 为选中的 submodule 处理可选配置
1118
1310
  for (const sub of selectedSubmodules) {
1119
- if (sub.optionalConfigs.length > 0 && !options.yes && process.stdout.isTTY) {
1311
+ if (sub.optionalConfigs.length === 0)
1312
+ continue;
1313
+ const rememberedConfigs = existingProject?.submoduleOptionalConfigs?.[sub.relativePath] ?? [];
1314
+ if (options.yes) {
1315
+ sub.selectedOptionalConfigs = sub.optionalConfigs.filter((configInfo) => rememberedConfigs.includes(configInfo.name));
1316
+ }
1317
+ else if (process.stdout.isTTY) {
1120
1318
  info(`${sub.name} 发现可选配置:`);
1121
- const selected = await selectOptionalConfigs(sub.optionalConfigs, { isTTY: true, specifiedConfigs: [] });
1319
+ const selected = await selectOptionalConfigs(sub.optionalConfigs, { isTTY: true, specifiedConfigs: rememberedConfigs });
1122
1320
  if (selected !== PROMPT_CANCELLED) {
1123
1321
  sub.selectedOptionalConfigs = selected;
1124
1322
  }
@@ -1184,7 +1382,9 @@ export async function linkProject(projectPath, options) {
1184
1382
  dryRun: options.dryRun,
1185
1383
  yes: options.yes,
1186
1384
  concurrency,
1385
+ gitLightweightDownload,
1187
1386
  optionalConfigs: selectedOptionalConfigs,
1387
+ skipActions: options.skipAction ?? [],
1188
1388
  });
1189
1389
  const finalLinkPlatforms = mainResult.finalLinkPlatforms;
1190
1390
  // 如果 dry-run 模式,linkScope 内已显示信息
@@ -1207,8 +1407,10 @@ export async function linkProject(projectPath, options) {
1207
1407
  dryRun: true,
1208
1408
  yes: options.yes,
1209
1409
  concurrency,
1410
+ gitLightweightDownload,
1210
1411
  optionalConfigs: sub.selectedOptionalConfigs,
1211
1412
  scope: sub.relativePath,
1413
+ skipActions: options.skipAction ?? [],
1212
1414
  });
1213
1415
  }
1214
1416
  return;
@@ -1232,20 +1434,14 @@ export async function linkProject(projectPath, options) {
1232
1434
  dryRun: options.dryRun,
1233
1435
  yes: options.yes,
1234
1436
  concurrency,
1437
+ gitLightweightDownload,
1235
1438
  optionalConfigs: sub.selectedOptionalConfigs,
1236
1439
  scope: sub.relativePath,
1440
+ skipActions: options.skipAction ?? [],
1237
1441
  });
1238
1442
  subResults.push(subResult);
1239
1443
  }
1240
1444
  // === 阶段 5: 合并结果、注册项目 ===
1241
- const allGeneralLibs = new Set([
1242
- ...mainResult.generalLibs,
1243
- ...subResults.flatMap(r => [...r.generalLibs]),
1244
- ]);
1245
- const allDownloadedLibs = [
1246
- ...mainResult.downloadedLibs,
1247
- ...subResults.flatMap(r => r.downloadedLibs),
1248
- ];
1249
1445
  // 合并所有依赖
1250
1446
  const allLinkedDeps = [
1251
1447
  ...mainResult.linkedDeps,
@@ -1266,6 +1462,7 @@ export async function linkProject(projectPath, options) {
1266
1462
  ? selectedOptionalConfigs.map(c => c.name) : undefined,
1267
1463
  submodules: selectedSubmodules.length > 0
1268
1464
  ? selectedSubmodules.map(s => s.relativePath) : undefined,
1465
+ submoduleOptionalConfigs: buildSubmoduleOptionalConfigState(selectedSubmodules),
1269
1466
  });
1270
1467
  // 更新 Store 引用关系
1271
1468
  const newStoreKeys = allLinkedDeps.map((d) => registry.getStoreKey(d.libName, d.commit, d.platform));
@@ -1416,10 +1613,7 @@ function hintStoreRepairCommand(err) {
1416
1613
  async function registerSharedStore(libName, commit, branch = '', url = '') {
1417
1614
  const registry = getRegistry();
1418
1615
  const storeKey = registry.getStoreKey(libName, commit, SHARED_PLATFORM);
1419
- // 如果已存在,不重复注册
1420
- if (registry.getStore(storeKey)) {
1421
- return true;
1422
- }
1616
+ const existingEntry = registry.getStore(storeKey);
1423
1617
  // 检查 _shared 目录是否存在
1424
1618
  const storePath = await config.getStorePath();
1425
1619
  if (!storePath)
@@ -1437,17 +1631,27 @@ async function registerSharedStore(libName, commit, branch = '', url = '') {
1437
1631
  }
1438
1632
  // 计算 _shared 完整性信息并注册
1439
1633
  const integrity = await store.captureIntegrity(libName, commit, SHARED_PLATFORM);
1440
- registry.addStore({
1441
- libName,
1442
- commit,
1443
- platform: SHARED_PLATFORM,
1444
- branch,
1445
- url,
1446
- ...integrity,
1447
- usedBy: [], // _shared 不追踪引用,生命周期跟随平台
1448
- createdAt: new Date().toISOString(),
1449
- lastAccess: new Date().toISOString(),
1450
- });
1634
+ if (existingEntry) {
1635
+ registry.updateStore(storeKey, {
1636
+ branch: existingEntry.branch || branch,
1637
+ url: existingEntry.url || url,
1638
+ ...integrity,
1639
+ lastAccess: new Date().toISOString(),
1640
+ });
1641
+ }
1642
+ else {
1643
+ registry.addStore({
1644
+ libName,
1645
+ commit,
1646
+ platform: SHARED_PLATFORM,
1647
+ branch,
1648
+ url,
1649
+ ...integrity,
1650
+ usedBy: [], // _shared 不追踪引用,生命周期跟随平台
1651
+ createdAt: new Date().toISOString(),
1652
+ lastAccess: new Date().toISOString(),
1653
+ });
1654
+ }
1451
1655
  debug(`注册 _shared: ${libName}:${commit.slice(0, 8)} (${formatSize(integrity.size)})`);
1452
1656
  return true;
1453
1657
  }
@@ -1473,6 +1677,13 @@ async function ensureStoreInfo(libName, commit, platform, branch, url, projectHa
1473
1677
  const integrity = await store.captureIntegrity(libName, commit, platform);
1474
1678
  registry.updateStore(storeKey, integrity);
1475
1679
  }
1680
+ if (existingEntry && ((!existingEntry.branch && branch) || (!existingEntry.url && url))) {
1681
+ registry.updateStore(storeKey, {
1682
+ branch: existingEntry.branch || branch,
1683
+ url: existingEntry.url || url,
1684
+ lastAccess: new Date().toISOString(),
1685
+ });
1686
+ }
1476
1687
  if (projectHash) {
1477
1688
  registry.addStoreReference(storeKey, projectHash);
1478
1689
  }
@@ -1713,7 +1924,7 @@ async function classifyDependencies(dependencies, projectPath, configPath, platf
1713
1924
  }
1714
1925
  const existing = verifiedExisting;
1715
1926
  // 也检查是否为 General 库(有 _shared 且有内容)
1716
- const isGeneral = await resolveLibraryGeneralState(dep);
1927
+ const isGeneral = await resolveLibraryGeneralState(dep, requestedPlatforms, vars);
1717
1928
  const inStore = existing.length > 0 || isGeneral;
1718
1929
  if (satisfiedRequested.length > 0) {
1719
1930
  clearSatisfiedAutoUnavailablePlatforms(getRegistry(), dep, satisfiedRequested);
@@ -1833,6 +2044,7 @@ async function supplementMissingPlatforms(dependency, platforms, registry, tx, o
1833
2044
  platforms: toDownload,
1834
2045
  sparse: dependency.sparse,
1835
2046
  vars: options.vars,
2047
+ useGitLightweightDownload: options.useGitLightweightDownload,
1836
2048
  });
1837
2049
  // 提示清理的平台(如果有)
1838
2050
  if (downloadResult.cleanedPlatforms.length > 0) {
@@ -1949,19 +2161,20 @@ function getStatusKey(status) {
1949
2161
  async function syncCacheFile(configPath) {
1950
2162
  const configDir = path.dirname(configPath);
1951
2163
  const cacheDir = path.join(configDir, '.cache');
1952
- const cachePath = path.join(cacheDir, 'codepac-dep.json');
2164
+ const configFileName = path.basename(configPath);
2165
+ const cachePath = path.join(cacheDir, configFileName);
1953
2166
  try {
1954
2167
  // 确保 .cache 目录存在
1955
2168
  await fs.mkdir(cacheDir, { recursive: true });
1956
2169
  // 复制配置文件到 cache
1957
2170
  await fs.copyFile(configPath, cachePath);
1958
2171
  if (process.env.VERBOSE) {
1959
- info(`已同步 cache: ${path.basename(configPath)}`);
2172
+ info(`已同步 cache: ${configFileName} -> ${path.relative(configDir, cachePath)}`);
1960
2173
  }
1961
2174
  }
1962
2175
  catch (err) {
1963
2176
  // cache 同步失败不应阻塞主流程,仅警告
1964
- warn(`cache 同步失败: ${err.message}`);
2177
+ warn(`cache 同步失败: ${configFileName} -> ${path.relative(configDir, cachePath)}: ${err.message}`);
1965
2178
  }
1966
2179
  }
1967
2180
  /**
@@ -1971,78 +2184,98 @@ async function syncCacheFile(configPath) {
1971
2184
  * @param thirdPartyDir 3rdparty 目录路径
1972
2185
  * @param options 处理选项
1973
2186
  */
1974
- async function processAction(action, context, thirdPartyDir, options) {
2187
+ async function processAction(action, context, parentConfigPath, parentTargetDir, options) {
1975
2188
  const indent = ' '.repeat(context.depth);
1976
- // 1. 解析 action 命令
1977
- let parsed;
2189
+ if (action.name && options.skipActions.has(action.name)) {
2190
+ info(`${indent}跳过嵌套 action: name=${action.name}, 原因=skip-action`);
2191
+ debug(`${indent}CodePac action 跳过: name=${action.name}, command=${action.command}, skipActions=${Array.from(options.skipActions).join(',')}`);
2192
+ return;
2193
+ }
2194
+ // 1. 解析 action 命令和执行计划
2195
+ let plan;
1978
2196
  try {
1979
- parsed = parseActionCommand(action.command);
2197
+ plan = buildActionExecutionPlan(action, {
2198
+ parentConfigPath,
2199
+ parentTargetDir,
2200
+ inheritedCodepacPlatforms: context.codepacPlatforms ?? context.platforms,
2201
+ });
1980
2202
  }
1981
2203
  catch (err) {
1982
- warn(`${indent}无法解析 action: ${err.message}`);
2204
+ warn(`${indent}跳过嵌套 action: name=${action.name ?? '(未命名)'}, 原因=无法解析命令, message=${err.message}`);
2205
+ debug(`${indent}CodePac action 解析失败: command=${action.command}`);
1983
2206
  return;
1984
2207
  }
1985
- const libsDisplay = parsed.libraries.length > 0 ? parsed.libraries.join(', ') : '全部依赖';
1986
- info(`${indent}处理嵌套依赖: ${parsed.configDir} [${libsDisplay}]`);
1987
- // 2. 构建嵌套配置路径
1988
- const nestedConfigPath = path.join(thirdPartyDir, parsed.configDir, 'codepac-dep.json');
1989
- // 3. 循环检测
1990
- if (context.processedConfigs.has(nestedConfigPath)) {
1991
- warn(`${indent} 检测到循环依赖,跳过: ${parsed.configDir}`);
2208
+ const libsDisplay = plan.libraries.length > 0 ? plan.libraries.join(', ') : '全部依赖';
2209
+ info(`${indent}处理嵌套 action: name=${plan.actionName ?? '(未命名)'}, config=${plan.nestedConfigPath}, target=${plan.nestedTargetDir}, libs=[${libsDisplay}]`);
2210
+ debug(`${indent}CodePac action 执行计划: command=${plan.command}, parentConfig=${parentConfigPath}, parentTarget=${parentTargetDir}, parsedConfigDir=${plan.configDir}, parsedTargetDir=${plan.targetDir}, resolvedConfig=${plan.nestedConfigPath}, resolvedTarget=${plan.nestedTargetDir}, inheritedPlatforms=${formatActionPlatforms(plan.inheritedPlatforms)}, effectivePlatforms=${formatActionPlatforms(plan.effectiveCodepacPlatforms)}, explicitPlatform=${plan.parsed.hasExplicitPlatform}, fullgit=${plan.parsed.hasExplicitFullGit}, unshallow=${plan.parsed.hasExplicitUnshallow}, disable_sparse=${plan.parsed.hasExplicitDisableSparse}`);
2211
+ // 2. 循环检测
2212
+ const visitKey = buildActionVisitKey(plan.nestedConfigPath, plan.nestedTargetDir);
2213
+ if (context.processedConfigs.has(visitKey)) {
2214
+ warn(`${indent} 跳过嵌套 action: name=${plan.actionName ?? '(未命名)'}, 原因=循环配置, config=${plan.nestedConfigPath}, target=${plan.nestedTargetDir}`);
1992
2215
  return;
1993
2216
  }
1994
- context.processedConfigs.add(nestedConfigPath);
1995
- // 4. 检查配置文件是否存在
2217
+ context.processedConfigs.add(visitKey);
2218
+ // 3. 检查配置文件是否存在
1996
2219
  try {
1997
- await fs.access(nestedConfigPath);
2220
+ await fs.access(plan.nestedConfigPath);
1998
2221
  }
1999
2222
  catch {
2000
- warn(`${indent} 嵌套配置文件不存在: ${nestedConfigPath}`);
2001
- hint(`${indent} 请确保 ${parsed.configDir} 库已被下载`);
2223
+ warn(`${indent} 跳过嵌套 action: name=${plan.actionName ?? '(未命名)'}, 原因=配置不存在, config=${plan.nestedConfigPath}`);
2224
+ hint(`${indent} 请确保 ${plan.configDir} 库已被下载`);
2002
2225
  return;
2003
2226
  }
2004
- // 5. 提取指定库的依赖
2227
+ // 4. 提取指定库的依赖
2005
2228
  let nestedResult;
2006
2229
  try {
2007
- nestedResult = await extractNestedDependencies(nestedConfigPath, parsed.libraries);
2230
+ nestedResult = await extractNestedDependencies(plan.nestedConfigPath, plan.libraries, {
2231
+ platforms: plan.effectiveCodepacPlatforms,
2232
+ configPath: plan.nestedConfigPath,
2233
+ });
2008
2234
  }
2009
2235
  catch (err) {
2010
- warn(`${indent} 解析嵌套配置失败: ${err.message}`);
2236
+ warn(`${indent} 跳过嵌套 action: name=${plan.actionName ?? '(未命名)'}, 原因=解析配置失败, message=${err.message}`);
2011
2237
  return;
2012
2238
  }
2013
2239
  const { dependencies, vars, nestedActions } = nestedResult;
2014
2240
  if (dependencies.length === 0) {
2015
- warn(`${indent} ${parsed.configDir} 中未找到指定的库`);
2241
+ warn(`${indent} 跳过嵌套 action: name=${plan.actionName ?? '(未命名)'}, 原因=未找到依赖, libs=[${libsDisplay}], config=${plan.nestedConfigPath}`);
2016
2242
  return;
2017
2243
  }
2018
- info(`${indent} 找到 ${dependencies.length} 个嵌套依赖`);
2019
- // 6. 合并变量
2244
+ info(`${indent} 嵌套 action 命中 ${dependencies.length} 个依赖,继承平台: ${formatActionPlatforms(plan.inheritedPlatforms)}, 生效平台: ${formatActionPlatforms(plan.effectiveCodepacPlatforms)}`);
2245
+ // 5. 合并变量
2020
2246
  const mergedVars = { ...context.vars, ...vars };
2021
- // 7. 处理这些依赖(targetDir 指定嵌套依赖的目标目录)
2247
+ // 6. 处理这些依赖(targetDir 指定嵌套依赖的目标目录)
2022
2248
  await linkNestedDependencies(dependencies, {
2023
- thirdPartyDir,
2024
- targetDir: parsed.targetDir,
2025
- nestedConfigPath,
2026
- context: { ...context, vars: mergedVars },
2249
+ targetDir: plan.nestedTargetDir,
2250
+ nestedConfigPath: plan.nestedConfigPath,
2251
+ context: { ...context, codepacPlatforms: plan.effectiveCodepacPlatforms, vars: mergedVars },
2027
2252
  options,
2028
2253
  indent,
2029
2254
  });
2030
2255
  // 同步嵌套配置文件的 cache(兼容 checkValid.js 检测)
2031
- await syncCacheFile(nestedConfigPath);
2032
- // 8. 递归处理嵌套 actions(如果没有 disable_action)
2033
- // 注意:递归时 thirdPartyDir 应该更新为当前嵌套依赖的目标目录
2034
- if (!parsed.disableAction && nestedActions.length > 0) {
2256
+ await syncCacheFile(plan.nestedConfigPath);
2257
+ // 7. 递归处理嵌套 actions(如果没有 disable_action)
2258
+ if (!plan.parsed.disableAction && nestedActions.length > 0) {
2035
2259
  const nestedContext = {
2036
2260
  depth: context.depth + 1,
2037
2261
  processedConfigs: context.processedConfigs,
2038
2262
  platforms: context.platforms,
2263
+ codepacPlatforms: plan.effectiveCodepacPlatforms,
2039
2264
  vars: mergedVars,
2040
2265
  };
2041
- const nestedThirdPartyDir = path.join(thirdPartyDir, parsed.targetDir);
2042
2266
  for (const nestedAction of nestedActions) {
2043
- await processAction(nestedAction, nestedContext, nestedThirdPartyDir, options);
2267
+ await processAction(nestedAction, nestedContext, plan.nestedConfigPath, plan.nestedTargetDir, options);
2044
2268
  }
2045
2269
  }
2270
+ else if (plan.parsed.disableAction && nestedActions.length > 0) {
2271
+ info(`${indent} 跳过后续嵌套 action: name=${plan.actionName ?? '(未命名)'}, 原因=disable_action`);
2272
+ }
2273
+ }
2274
+ function formatActionPlatforms(platforms) {
2275
+ return platforms && platforms.length > 0 ? platforms.join(',') : 'all';
2276
+ }
2277
+ function buildActionVisitKey(configPath, targetDir) {
2278
+ return `${configPath}::${targetDir}`;
2046
2279
  }
2047
2280
  /**
2048
2281
  * 统一的本地目录验证+吸收流程
@@ -2100,9 +2333,7 @@ async function resolveLocalAndAbsorb(localPath, libName, commit, options) {
2100
2333
  * 链接嵌套依赖
2101
2334
  */
2102
2335
  async function linkNestedDependencies(dependencies, params) {
2103
- const { thirdPartyDir, targetDir, nestedConfigPath, context, options, indent } = params;
2104
- // 计算嵌套依赖的实际目标目录
2105
- const nestedTargetDir = path.join(thirdPartyDir, targetDir);
2336
+ const { targetDir: nestedTargetDir, context, options, indent } = params;
2106
2337
  const { tx, registry, projectHash, projectRoot, dryRun, download, generalLibs, downloadedLibs, nestedLinkedDeps } = options;
2107
2338
  const { platforms, vars } = context;
2108
2339
  for (const dep of dependencies) {
@@ -2121,14 +2352,12 @@ async function linkNestedDependencies(dependencies, params) {
2121
2352
  // 不存在
2122
2353
  }
2123
2354
  // 检查 Store 状态和历史记录
2124
- const libKey = registry.getLibraryKey(dep.libName, dep.commit);
2125
- const historyLib = registry.getLibrary(libKey);
2126
2355
  const unavailablePlatforms = getBlockedRequestedPlatforms(registry, dep, platforms, vars);
2127
2356
  // 过滤掉已知不可用的平台
2128
2357
  const availablePlatforms = platforms.filter(p => !unavailablePlatforms.includes(p));
2129
2358
  const knownUnavailable = platforms.filter(p => unavailablePlatforms.includes(p));
2130
2359
  let storeHas = false;
2131
- const existingPlatforms = [];
2360
+ let existingPlatforms = [];
2132
2361
  const initialStoreState = await resolveRequestedStoreState(dep, availablePlatforms, vars);
2133
2362
  existingPlatforms.push(...initialStoreState.actualExisting);
2134
2363
  if (initialStoreState.satisfiedRequested.length > 0) {
@@ -2138,6 +2367,7 @@ async function linkNestedDependencies(dependencies, params) {
2138
2367
  // 完整性校验:验证 existing 平台的文件完整性(与 classifyDependencies 同逻辑)
2139
2368
  if (existingPlatforms.length > 0) {
2140
2369
  const { valid: verifiedPlatforms, corrupted } = await verifyExistingPlatforms(dep.libName, dep.commit, existingPlatforms);
2370
+ existingPlatforms = verifiedPlatforms;
2141
2371
  if (corrupted.length > 0) {
2142
2372
  for (const p of corrupted) {
2143
2373
  const storeKey = registry.getStoreKey(dep.libName, dep.commit, p);
@@ -2145,10 +2375,10 @@ async function linkNestedDependencies(dependencies, params) {
2145
2375
  const platformPath = store.getLibraryPath(await store.getStorePath(), dep.libName, dep.commit, p);
2146
2376
  await fs.rm(platformPath, { recursive: true, force: true }).catch(() => { });
2147
2377
  }
2148
- storeHas = verifiedPlatforms.length > 0;
2149
2378
  }
2379
+ storeHas = existingPlatforms.length > 0;
2150
2380
  }
2151
- let isGeneral = await resolveLibraryGeneralState(dep);
2381
+ let isGeneral = await resolveLibraryGeneralState(dep, availablePlatforms, vars);
2152
2382
  // 如果所有平台都已知不可用,跳过
2153
2383
  if (availablePlatforms.length === 0 && knownUnavailable.length > 0 && !isGeneral) {
2154
2384
  warn(`${indent} ${dep.libName} - 平台 [${knownUnavailable.join(', ')}] 已按规则跳过`);
@@ -2169,10 +2399,21 @@ async function linkNestedDependencies(dependencies, params) {
2169
2399
  if (resolveResult.isGeneral) {
2170
2400
  isGeneral = true;
2171
2401
  }
2402
+ else if (resolveResult.absorbResult) {
2403
+ const absorbedPlatforms = resolveResult.absorbResult.platforms;
2404
+ const absorbedPlatformSet = new Set(absorbedPlatforms);
2405
+ const linkableAbsorbedPlatforms = [...new Set(availablePlatforms.flatMap((platform) => getRequestedPlatformTargets(platform, dep.sparse, vars)
2406
+ .filter((target) => absorbedPlatformSet.has(target))))];
2407
+ existingPlatforms = [...new Set([
2408
+ ...existingPlatforms,
2409
+ ...linkableAbsorbedPlatforms,
2410
+ ])];
2411
+ debug(`${indent} ${dep.libName} - 本地吸收后平台回填: requested=${availablePlatforms.join(', ') || '无'}, absorbed=${absorbedPlatforms.join(', ') || '无'}, linkable=${existingPlatforms.join(', ') || '无'}`);
2412
+ }
2172
2413
  if (resolveResult.absorbResult?.nestedLibraries.length) {
2173
2414
  await registerNestedLibraries(resolveResult.absorbResult.nestedLibraries, projectHash);
2174
2415
  }
2175
- storeHas = true;
2416
+ storeHas = isGeneral || existingPlatforms.length > 0;
2176
2417
  // 吸收为 General 后,检查是否实际需要平台内容
2177
2418
  // 本地可能只有部分文件(如只有 _shared 内容),被误分类为 General
2178
2419
  if (resolveResult.isGeneral && download) {
@@ -2187,6 +2428,7 @@ async function linkNestedDependencies(dependencies, params) {
2187
2428
  platforms: availablePlatforms,
2188
2429
  sparse: dep.sparse,
2189
2430
  vars,
2431
+ useGitLightweightDownload: options.gitLightweightDownload,
2190
2432
  });
2191
2433
  const assessment = assessDownloadResult(availablePlatforms, dep.sparse, downloadResult, vars);
2192
2434
  clearSatisfiedAutoUnavailablePlatforms(registry, dep, assessment.satisfiedRequested);
@@ -2226,7 +2468,7 @@ async function linkNestedDependencies(dependencies, params) {
2226
2468
  let linkedPlatforms = isGeneral ? [GENERAL_PLATFORM] : [...existingPlatforms];
2227
2469
  // 已链接,但需要检查是否需要补充缺失平台(与顶层依赖逻辑一致)
2228
2470
  if (!isGeneral) {
2229
- const supplementResult = await supplementMissingPlatforms(dep, platforms, registry, tx, { vars });
2471
+ const supplementResult = await supplementMissingPlatforms(dep, platforms, registry, tx, { vars, useGitLightweightDownload: options.gitLightweightDownload });
2230
2472
  // 注册嵌套依赖
2231
2473
  if (supplementResult.nestedLibraries.length > 0) {
2232
2474
  await registerNestedLibraries(supplementResult.nestedLibraries, projectHash);
@@ -2297,7 +2539,7 @@ async function linkNestedDependencies(dependencies, params) {
2297
2539
  }
2298
2540
  else {
2299
2541
  // 平台库:先补充缺失平台(与顶层依赖逻辑一致)
2300
- const supplementResult = await supplementMissingPlatforms(dep, platforms, registry, tx, { vars });
2542
+ const supplementResult = await supplementMissingPlatforms(dep, platforms, registry, tx, { vars, useGitLightweightDownload: options.gitLightweightDownload });
2301
2543
  // 注册嵌套依赖
2302
2544
  if (supplementResult.nestedLibraries.length > 0) {
2303
2545
  await registerNestedLibraries(supplementResult.nestedLibraries, projectHash);
@@ -2346,6 +2588,7 @@ async function linkNestedDependencies(dependencies, params) {
2346
2588
  platforms: availablePlatforms,
2347
2589
  sparse: dep.sparse,
2348
2590
  vars,
2591
+ useGitLightweightDownload: options.gitLightweightDownload,
2349
2592
  onTempDirCreated: (_tempDir, libDir) => { downloadMonitor.start(libDir); },
2350
2593
  onHeartbeat: (message) => { downloadMonitor.heartbeat(message); },
2351
2594
  });
@@ -2477,26 +2720,62 @@ export async function verifyExistingPlatforms(libName, commit, existingPlatforms
2477
2720
  for (const platform of existingPlatforms) {
2478
2721
  const storeKey = registry.getStoreKey(libName, commit, platform);
2479
2722
  const entry = registry.getStore(storeKey);
2480
- // 没有 StoreEntry,跳过校验视为合法
2481
2723
  if (!entry) {
2724
+ try {
2725
+ const integrity = await store.captureIntegrity(libName, commit, platform);
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
+ }
2482
2748
  valid.push(platform);
2483
2749
  continue;
2484
2750
  }
2751
+ if (entry.fileCount === 0) {
2752
+ corrupted.push(platform);
2753
+ warn(`${libName} (${commit.slice(0, 7)}) ${platform}: Store 记录为空`);
2754
+ continue;
2755
+ }
2485
2756
  // 旧数据回填:升级后首次遇到无 fileCount 的条目,顺便记录完整性数据
2486
2757
  if (entry.fileCount == null) {
2487
2758
  try {
2488
2759
  const integrity = await store.captureIntegrity(libName, commit, platform);
2489
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
+ }
2490
2766
  }
2491
2767
  catch {
2492
- // 回填失败不影响正常流程
2768
+ corrupted.push(platform);
2769
+ warn(`${libName} (${commit.slice(0, 7)}) ${platform}: Store 完整性回填失败`);
2770
+ continue;
2493
2771
  }
2494
2772
  valid.push(platform);
2495
2773
  continue;
2496
2774
  }
2497
- const result = await store.quickVerify(libName, commit, platform, {
2775
+ const result = await store.fullVerify(libName, commit, platform, {
2498
2776
  size: entry.size,
2499
2777
  fileCount: entry.fileCount,
2778
+ contentHash: entry.contentHash,
2500
2779
  });
2501
2780
  if (result.valid) {
2502
2781
  valid.push(platform);